C/C++向量快速转换为Numpy数组

6 投票
4 回答
4535 浏览
提问于 2025-04-16 14:21

我正在使用SWIG把一些C++代码和Python(2.6)连接在一起,其中一部分代码是将大量数据(几百万个值)从C++转换成Numpy数组。我想到的最好方法是为这个类实现一个迭代器,然后提供一个Python方法:

def __array__(self, dtype=float):
    return np.fromiter(self, dtype, self.size())

问题是,每次调用迭代器的next方法都非常耗时,因为它需要经过大约三到四个SWIG的包装。这花费的时间太长了。我可以保证C++的数据是连续存储的(因为它们在一个std::vector里),我觉得Numpy应该能够直接获取数据的起始位置和数据的数量,然后直接读取这些数据。

有没有办法把internal_data_[0]的指针和internal_data_.size()的值传给Numpy,这样它就可以直接访问或复制数据,而不需要所有Python的额外开销呢?

4 个回答

1

也许可以用 f2py 来代替 swig。虽然它名字里有个“fortran”,但其实它也能把 Python 和 C 连接起来。你可以看看这个链接:http://www.scipy.org/Cookbook/f2py_and_NumPy

它的好处是可以自动处理转换成 numpy 数组的过程。

不过有两个需要注意的地方:如果你还不懂 Fortran,可能会觉得 f2py 有点奇怪;而且我不太清楚它和 C++ 的兼容性如何。

2

你应该定义一下 __array_interface__() 这个东西。这样你就可以直接传回指针和形状信息了。

0

看起来唯一真正的解决办法是基于 pybuffer.i 来实现一个可以把 C++ 的数据复制到现有缓冲区的功能。如果你把这个内容添加到 SWIG 的包含文件中:

%insert("python") %{
import numpy as np
%}

/*! Templated function to copy contents of a container to an allocated memory
 * buffer
 */
%inline %{
//==== ADDED BY numpy.i
#include <algorithm>

template < typename Container_T >
void copy_to_buffer(
        const Container_T& field,
        typename Container_T::value_type* buffer,
        typename Container_T::size_type length
        )
{
//    ValidateUserInput( length == field.size(),
//            "Destination buffer is the wrong size" );
    // put your own assertion here or BAD THINGS CAN HAPPEN

    if (length == field.size()) {
        std::copy( field.begin(), field.end(), buffer );
    }
}
//====

%}

%define TYPEMAP_COPY_TO_BUFFER(CLASS...)
%typemap(in) (CLASS::value_type* buffer, CLASS::size_type length)
(int res = 0, Py_ssize_t size_ = 0, void *buffer_ = 0) {

    res = PyObject_AsWriteBuffer($input, &buffer_, &size_);
    if ( res < 0 ) {
        PyErr_Clear();
        %argument_fail(res, "(CLASS::value_type*, CLASS::size_type length)",
                $symname, $argnum);
    }
    $1 = ($1_ltype) buffer_;
    $2 = ($2_ltype) (size_/sizeof($*1_type));
}
%enddef


%define ADD_NUMPY_ARRAY_INTERFACE(PYVALUE, PYCLASS, CLASS...)

TYPEMAP_COPY_TO_BUFFER(CLASS)

%template(_copy_to_buffer_ ## PYCLASS) copy_to_buffer< CLASS >;

%extend CLASS {
%insert("python") %{
def __array__(self):
    """Enable access to this data as a numpy array"""
    a = np.ndarray( shape=( len(self), ), dtype=PYVALUE )
    _copy_to_buffer_ ## PYCLASS(self, a)
    return a
%}
}

%enddef

那么你就可以让一个容器变得可以被 "Numpy" 使用,方法是:

%template(DumbVectorFloat) DumbVector<double>;
ADD_NUMPY_ARRAY_INTERFACE(float, DumbVectorFloat, DumbVector<double>);

然后在 Python 中,只需要这样做:

# dvf is an instance of DumbVectorFloat
import numpy as np
my_numpy_array = np.asarray( dvf )

这样做的开销只有一次 Python 和 C++ 之间的转换调用,而不是通常情况下处理一个长度为 N 的数组所带来的多次调用。

这个代码的一个稍微完整的版本可以在我的 PyTRT 项目在 GitHub 上 找到。

撰写回答