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

6

我正在使用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个回答

2

你能提供一些实际实现的细节吗?还有一种方法可以在不针对Numpy头文件编译我的项目的情况下完成吗?谢谢。 - Seth Johnson
它还说那是一个遗留接口。 - Seth Johnson
__array_interface__只是一个普通的字典,其中包含普通类型。不需要使用任何Numpy头文件进行编译。忽略将其称为“遗留”的注释。我以为我已经删除了它。如果您愿意,可以实现PEP 3118缓冲区接口,但这更容易。 - Robert Kern

1

也许可以使用f2py代替swig。尽管它的名称如此,但它能够将Python与C以及Fortran进行接口。请参见http://www.scipy.org/Cookbook/f2py_and_NumPy

优点是它可以自动处理转换为numpy数组。

两个注意事项:如果您不知道Fortran,您可能会觉得f2py有点奇怪;我不知道它在C++上的表现如何。


谢谢回复。我确实了解一些FORTRAN,但是我的代码中使用了很多C++的特性:模板、typedef等。我也不想引入另一个依赖项。 - Seth Johnson
关于C++,说得很好。你可能需要编写中间的纯C包装器,这可能会很麻烦。另一方面,它并不是真正的另一个依赖项,因为f2py是numpy的一部分,而你已经在使用它了。你不需要Fortran编译器。 - deprecated

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的数组所产生的N个调用。

这段代码的稍微完整版本是我在github上的PyTRT项目的一部分。


0
如果您将向量封装在实现了Python 缓冲接口的对象中,您可以将其传递给numpy数组进行初始化(参见文档,第三个参数)。我敢打赌这种初始化速度会快得多,因为它可以直接使用memcpy来复制数据。

谢谢您的提示。您有使用pybuffer_mutable_binary或其他接口在SWIG中实现__buffer__接口(例如浮点数)的例子吗? - Seth Johnson
@Seth:抱歉,我无法在那方面帮助你。 - Björn Pollex
看起来我需要手动从头开始实现整个缓冲区接口以便用于这个类。SWIG只提供了读取其他缓冲区的功能,而没有导出缓冲区函数的能力。 - Seth Johnson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接