Python 通过C&x2B+;通过Cython向Numpy发送矢量,无需复制并自动管理内存

Python 通过C&x2B+;通过Cython向Numpy发送矢量,无需复制并自动管理内存,python,c++,numpy,cython,Python,C++,Numpy,Cython,处理处理大型矩阵(NxM和1K当您从doit返回时,WhyNot对象超出范围,数组元素被释放。这意味着和WhyNot[0]不再是有效指针。您需要将WhyNot对象存储在其他地方,可能是调用方提供的位置 一种方法是将doit拆分为三个函数,doit\u allocate分配向量并返回指向它的指针,doit与前面一样(但使用一个参数接收指向预分配向量的指针,以及doit\u free`释放向量) 大概是这样的: vector<int> * doit_allocate() { re

处理处理大型矩阵(NxM和1K当您从
doit
返回时,
WhyNot
对象超出范围,数组元素被释放。这意味着
和WhyNot[0]
不再是有效指针。您需要将
WhyNot
对象存储在其他地方,可能是调用方提供的位置

一种方法是将
doit
拆分为三个函数,
doit\u allocate
分配向量并返回指向它的指针,
doit
与前面一样(但使用一个参数接收指向预分配向量的指针
,以及
doit\u free`释放向量)

大概是这样的:

vector<int> *
doit_allocate()
{
    return new vector<int>;
}

int *
doit(vector<int> *WhyNot, int length)
{
    // Something really heavy
    cout << "C++: doing it fast " << endl; 

    // Heavy stuff - like reading a big file and preprocessing it
    for(int i=0; i<length; ++i)
        WhyNot->push_back(i); // heavy stuff

    cout << "C++: did it really fast" << endl;
    return WhyNot->front();
}

void
doit_free(vector<int> *WhyNot)
{
    delete WhyNot;
}
向量*
doit_allocate()
{
返回新向量;
}
int*
doit(向量*WhyNot,整数长度)
{
//非常重的东西

Cuth

我认为FlorianWeimer的答案提供了一个不错的解决方案(分配<代码>向量<代码>并将其传递到C++函数中),但是应该可以从<代码> doIT < />代码中返回一个向量,并通过使用移动构造函数来避免拷贝。

from libcpp.vector cimport vector

cdef extern from "<utility>" namespace "std" nogil:
  T move[T](T) # don't worry that this doesn't quite match the c++ signature

cdef extern from "fast.h":
    vector[int] doit(int length)

# define ArrayWrapper as holding in a vector
cdef class ArrayWrapper:
    cdef vector[int] vec
    cdef Py_ssize_t shape[1]
    cdef Py_ssize_t strides[1]

    # constructor and destructor are fairly unimportant now since
    # vec will be destroyed automatically.

    cdef set_data(self, vector[int]& data):
       self.vec = move(data)
       # @ead suggests `self.vec.swap(data)` instead
       # to avoid having to wrap move

    # now implement the buffer protocol for the class
    # which makes it generally useful to anything that expects an array
    def __getbuffer__(self, Py_buffer *buffer, int flags):
        # relevant documentation http://cython.readthedocs.io/en/latest/src/userguide/buffer.html#a-matrix-class
        cdef Py_ssize_t itemsize = sizeof(self.vec[0])

        self.shape[0] = self.vec.size()
        self.strides[0] = sizeof(int)
        buffer.buf = <char *>&(self.vec[0])
        buffer.format = 'i'
        buffer.internal = NULL
        buffer.itemsize = itemsize
        buffer.len = self.v.size() * itemsize   # product(shape) * itemsize
        buffer.ndim = 1
        buffer.obj = self
        buffer.readonly = 0
        buffer.shape = self.shape
        buffer.strides = self.strides
        buffer.suboffsets = NULL

< > >强> > <强> > Cython在C++模板上不是很好,它坚持编写<代码> STD::移动(…)<代码>,而不是<代码> STD::移动(…)让C++推断出这些类型。这有时会导致问题:<代码> STD::移动< /代码>。如果你有问题,那么最好的解决方法通常是告诉Cython只需要你想要的重载:

 cdef extern from "<utility>" namespace "std" nogil:
    vector[int] move(vector[int])
cdef-extern来自“名称空间”std“nogil:
向量[int]移动(向量[int])

@AndyG您认为这是什么时候发生的?当第二次调用
doit
函数时?这不会启动一个新的向量吗?或者它基本上是在调整以前填充的向量的大小?如果是,为什么?对不起,我以前没有看过您的代码。您的代码有未定义的行为,因为它返回了对tem的引用临时。向量在
doit
之后超出范围。数组也会有同样的问题(我假设
std::array
在这里)。如果你遇到了分段错误,你可以计算你的祝福,因为它只会默默地给你垃圾。使用动态分配的数组(C型数组)你不会有这个问题,因为内存没有被释放。我想你可以把内存交给Cython拥有,否则你会有内存leaks@AndyG我明白了,是的,这很有意义。动态数组(C样式数组)正如我所提到的,工作正常。我是否可以避免将向量复制到数组中以返回?这不是一种理想的方式。C样式的数组似乎是最好的方法,请确保Cython正确管理内存。以防有人读到这一点-我认为在这里实现堆创建没有错:replaci使用
WhyNot->pushback(i);
并返回
WhyNot->data();
?是的。如果你能详细说明如何在Cython中实现这一点,那就太好了。例如,对代码做了什么修改?我从来没有在Cython中处理过向量。谢谢你的编辑。我的意思是如何在Cython中处理vector to numpy。@DavidW的回答似乎解决了这个问题。太棒了!只有几个问题。1-关于类型的好文档或源代码在哪里缓冲区格式获取的值,如“i”、“f”等?2-因此从现在开始numpy负责释放内存?1.缓冲区格式基本上与numpy阵列模块使用的格式匹配。2.是的-在此方案中,
ArrayRapper
保存内存,numpy在需要时保持对该内存的引用处于活动状态,因此无需执行任何操作我试图使用你的数组包装器,我的编译器抱怨move'move'不是'std'的成员。还有一个问题,如果我们将向量[int]数组直接传递给numpy_数组怎么办?例如,numpy_数组=np.asarray(array)这就是我在Python类属性中返回C++向量的方法。它是错误的吗?移动是C++标准中的一个相对最近的附加(C++ 11,我想)因此,如果您使用的是较旧的编译器,它可能无法工作。但可能有一个选项可以传递给编译器以启用它。如果您将向量直接传递给numpy数组,它将被复制两次,首先复制到Python列表,然后复制到numpy数组。这没有错,但如果向量较大,则复制效率会很低。还可以使用
std::vector.swap
而不是
std::move
(这可能是现成的)。
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
import numpy 

ext_modules = [Extension(
    "faster", 
    ["faster.pyx", "fast.cpp"], 
    language='c++',
    extra_compile_args=["-std=c++11"],
    extra_link_args=["-std=c++11"]
)]

setup(
    cmdclass = {'build_ext': build_ext}, 
    ext_modules = ext_modules,
    include_dirs=[numpy.get_include()]
)
python setup.py build_ext --inplace
>>> from faster import faster
>>> a = faster(1000000)
Cython: calling C++ function to do it
C++: doing it fast
C++: did it really fast
Cython: back from C++
Ctyhon: array wrapper set
Cython: __array__ called
Cython: __array__ done
Cython: all done - returning
>>> a = faster(1000000)
Cython: calling C++ function to do it
C++: doing it fast
C++: did it really fast
Cython: back from C++
Ctyhon: array wrapper set
Cython: __array__ called
Cython: __array__ done
Cython: all done - returning
Cython: __dealloc__ called
Segmentation fault (core dumped)
Cython: calling C++ function to do it
C++: doing it fast
C++: did it really fast
Cython: back from C++
Ctyhon: array wrapper set
Cython: __array__ called
Cython: __array__ done
Cython: all done - returning
Cython: __dealloc__ called <--- Perhaps this happened too early or late?
*** Error in 'python': double free or corruption (fasttop): 0x0000000001365570 ***
======= Backtrace: =========
More info here ....
vector<int> *
doit_allocate()
{
    return new vector<int>;
}

int *
doit(vector<int> *WhyNot, int length)
{
    // Something really heavy
    cout << "C++: doing it fast " << endl; 

    // Heavy stuff - like reading a big file and preprocessing it
    for(int i=0; i<length; ++i)
        WhyNot->push_back(i); // heavy stuff

    cout << "C++: did it really fast" << endl;
    return WhyNot->front();
}

void
doit_free(vector<int> *WhyNot)
{
    delete WhyNot;
}
from libcpp.vector cimport vector

cdef extern from "<utility>" namespace "std" nogil:
  T move[T](T) # don't worry that this doesn't quite match the c++ signature

cdef extern from "fast.h":
    vector[int] doit(int length)

# define ArrayWrapper as holding in a vector
cdef class ArrayWrapper:
    cdef vector[int] vec
    cdef Py_ssize_t shape[1]
    cdef Py_ssize_t strides[1]

    # constructor and destructor are fairly unimportant now since
    # vec will be destroyed automatically.

    cdef set_data(self, vector[int]& data):
       self.vec = move(data)
       # @ead suggests `self.vec.swap(data)` instead
       # to avoid having to wrap move

    # now implement the buffer protocol for the class
    # which makes it generally useful to anything that expects an array
    def __getbuffer__(self, Py_buffer *buffer, int flags):
        # relevant documentation http://cython.readthedocs.io/en/latest/src/userguide/buffer.html#a-matrix-class
        cdef Py_ssize_t itemsize = sizeof(self.vec[0])

        self.shape[0] = self.vec.size()
        self.strides[0] = sizeof(int)
        buffer.buf = <char *>&(self.vec[0])
        buffer.format = 'i'
        buffer.internal = NULL
        buffer.itemsize = itemsize
        buffer.len = self.v.size() * itemsize   # product(shape) * itemsize
        buffer.ndim = 1
        buffer.obj = self
        buffer.readonly = 0
        buffer.shape = self.shape
        buffer.strides = self.strides
        buffer.suboffsets = NULL
cdef vector[int] array = doit(length)
cdef ArrayWrapper w
w.set_data(array) # "array" itself is invalid from here on
numpy_array = np.asarray(w)
 cdef extern from "<utility>" namespace "std" nogil:
    vector[int] move(vector[int])