在Cython中处理C++数组(使用numpy和pytorch)

Bob*_*Bob 3 c++ numpy cython fasttext pytorch

我试图用来cython包装一个C++库(fastText如果相关的话).C++库类从磁盘加载一个非常大的数组.我的包装器从C++库中实例化一个类来加载数组,然后使用cython内存视图并将numpy.asarray数组转换为numpy数组,然后调用torch.from_numpy以创建张量.

出现的问题是如何处理为阵列释放内存.

现在,我得到pointer being freed was not allocated程序退出时.我希望这是因为C++代码和numpy/ pytorch都试图管理同一块RAM.

我可以简单地在C++库中注释掉析构函数,但这感觉就像它将导致我在路上遇到不同的问题.

我该如何处理这个问题?是否有任何关于如何使用C++处理内存共享的最佳实践文档cython

如果我修改C++库来包装在一个阵列shared_ptr,将cython(和numpy,pytorch等)共享shared_ptr正确?

如果问题是天真的,我道歉; Python垃圾收集对我来说非常神秘.

任何建议表示赞赏.

Dav*_*idW 5

我可以想到三种合理的方法.我将在下面概述它们(即没有任何代码将完整,但希望它将很清楚如何完成它).

1. C++拥有内存; Cython/Python拥有一个指向C++类的共享指针

(这看起来就像你一直在思考的那条线).

首先创建一个包含共享指针的Cython类

from libcpp.memory cimport shared_ptr

cdef class Holder:
    cdef shared_ptr[cpp_class] ptr

    @staticmethod
    cdef make_holder(shared_ptr[cpp_class] ptr):
       cdef holder = Holder() # empty class
       holder.ptr = ptr
       return holder
Run Code Online (Sandbox Code Playgroud)

然后,您需要为其定义缓冲区协议Holder.这允许以cpp_classnumpy数组和Cython内存视图都能理解的方式直接访问分配的内存.因此,他们持有对Holder实例的引用,而实例又保持cpp_class活着.(np.asarray(holder_instance)用于创建使用实例内存的numpy数组)

缓冲协议有点涉及,但Cython有相当广泛的文档,你应该在很大程度上能够复制和粘贴他们的例子.您需要添加的两种方法Holder__getbuffer____releasebuffer__.

2. Python拥有内存; 您的C++类包含指向Python对象的指针

在此版本中,您将内存分配为numpy数组(使用Python C API接口).当您的C++类以递减方式销毁数组的引用计数时,但是如果Python保存对该数组的引用,那么该数组可以比C++类更长.

#include <numpy/arrayobject.h>
#include <Python.h>

class cpp_class {
   private:
     PyObject* arr;
     double* data;
   public:
     cpp_class() {
       arr = PyArray_SimpleNew(...); // details left to be filled in
       data = PyArray_DATA(reinterpret_cast<PyArrayObject*>(arr));
       # fill in the data
     }

     ~cpp_class() {
         Py_DECREF(arr); // release our reference to it
     }

     PyObject* get_np_array() {
         Py_INCREF(arr); // Cython expects this to be done before it receives a PyObject
         return arr;
     }
};
Run Code Online (Sandbox Code Playgroud)

有关如何从C/C++分配numpy数组的详细信息,请参阅numpy文档.如果定义复制/移动构造函数,请注意引用计数.

Cython包装器然后看起来像:

cdef extern from "some_header.hpp":
    cdef cppclass cpp_class:
       # whatever constructors you want to allow
       object get_np_array()
Run Code Online (Sandbox Code Playgroud)

3. C++将数据的所有权转移到Python/Cython

在这个方案中,C++分配数组,但Cython/Python负责解除分配.一旦所有权转移,C++就不再能够访问数据.

class cpp_class {
   public:
     double* data; // for simplicity this is public - you may want to use accessors
     cpp_class() :
     data(new double[50])
     {/* fill the array as needed */}

     ~cpp_class() {
       delete [] data;
     }
};

// helper function for Cython
inline void del_cpp_array(double* a) {
   delete [] a;
}
Run Code Online (Sandbox Code Playgroud)

然后使用cython.view.array该类捕获分配的内存.这有一个用于销毁的回调函数:

from cython cimport view

cdef extern from "some_header.hpp":
   cdef cppclass cpp_class:
      double* data
      # whatever constructors and other functions
   void del_cpp_array(double*)

# later
cdef cpp_class cpp_instance # create this however you like
# ...
# modify line below to match your data
arr = view.array(shape=(10, 2), itemsize=sizeof(double), format="d",
                 mode="C", allocate_buffer=False)
arr.data = <char*>cpp_instance.data
cpp_instance.data = None # reset to NULL pointer
arr.callback_free_data = del_cpp_array
Run Code Online (Sandbox Code Playgroud)

arr 然后可以与memoryview或numpy数组一起使用.

你可能不得不乱用void*char*使用del_cpp_array- 我不确定Cython接口需要什么类型.


第一个选项可能是大多数要实现的工作,但几乎不需要对C++代码进行任何更改.第二个选项可能需要更改您不想要的C++代码.第三种选择很简单,但意味着C++不再能访问数据,这可能是一个缺点.