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

标签 c++ numpy cython pytorch fasttext

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

出现的问题是如何处理为数组分配内存。

现在,当程序退出时,我得到 pointer being freed was not allocated。我预计这是因为 C++ 代码和 numpy/pytorch 都试图管理相同的 RAM block 。

我可以简单地注释掉 C++ 库中的析构函数,但感觉它会给我带来不同的问题。

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

如果我修改 C++ 库以将数组包装在 shared_ptr 中,cython(和 numpypytorch 等)正确共享 shared_ptr

如果这个问题很幼稚,我深表歉意; Python 垃圾回收对我来说非常神秘。

欢迎任何建议。

最佳答案

我可以想到三种明智的做法。我将在下面概述它们(即没有任何代码是完整的,但希望它会清楚如何完成它)。

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

然后您需要为Holder 定义缓冲区协议(protocol)。这允许以 numpy 数组和 Cython 内存 View 都可以理解的方式直接访问由 cpp_class 分配的内存。因此,它们持有对 Holder 实例的引用,该实例又使 cpp_class 保持事件状态。 (使用 np.asarray(holder_instance) 创建一个使用实例内存的 numpy 数组)

缓冲协议(protocol)有点复杂,但 Cython 有 fairly extensive documentation并且您在很大程度上应该能够复制和粘贴他们的示例。您需要添加到 Holder 的两个方法是 __getbuffer____releasebuffer__

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

在此版本中,您将内存分配为 numpy 数组(使用 Python C API 接口(interface))。当您的 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;
     }
};

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

Cython 包装器看起来像:

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

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;
}

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

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

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

void*char*del_cpp_array 的转换你可能不得不搞砸一些 - 我不确定到底是什么Cython 接口(interface)需要的类型。


第一个选项可能是最难实现的,但需要对 C++ 代码进行少量更改。第二个选项可能需要更改您不想进行的 C++ 代码。第三个选项很简单,但意味着 C++ 不再能够访问数据,这可能是一个缺点。

关于c++ - 在 Cython 中处理 C++ 数组(使用 numpy 和 pytorch),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44396749/

相关文章:

C++线程: shared memory not updated despite absence of race

python - Numpy int 位长

python - python 错误的 cython 包装器中的 AttributeError

python-3.x - pxd 相对 cimport 适用于 language_level 2,但不适用于 language_level 3

c++ - 在使用 std::stod 之前初始化 QApplication 的奇怪错误

c++ - 在 C++ 中将 Double 转换为 String 时精度丢失

c++ - std::multimap 中的唯一键是否存在迭代器?

Python/Numpy 内存错误

python - 无法计算两个日期之间的工作日?将 dtype ('<M8[ns]' ) 转换为 dtype ('<M8[D]' )?

cython - 使用函数指针指向没有 gil 的类的方法