在 Cython 中处理 C++ 数组(使用 numpy 和 pytorch)
Handling C++ arrays in Cython (with numpy and pytorch)
我正在尝试使用 cython
来包装 C++ 库(fastText
,如果相关的话)。 C++ 库 classes 从磁盘加载一个非常大的数组。我的包装器从 C++ 库实例化一个 class 来加载数组,然后使用 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 垃圾收集对我来说很神秘。
如有任何建议,我们将不胜感激。
我可以想到三种明智的做法。我将在下面概述它们(即 none 的代码将是完整的,但希望它会清楚如何完成它)。
1。 C++ 拥有内存; Cython/Python 持有指向 C++ 的共享指针 class
(这看起来是您已经在思考的路线)。
首先创建一个包含共享指针的 Cython class
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
定义缓冲协议。这允许以 numpy 数组和 Cython 内存视图都可以理解的方式直接访问由 cpp_class
分配的内存。因此,它们持有对 Holder
实例的引用,该实例又使 cpp_class
保持活动状态。 (使用np.asarray(holder_instance)
创建一个使用实例内存的numpy数组)
缓冲协议有点复杂,但 Cython 有 fairly extensive documentation,您应该能够在很大程度上复制和粘贴他们的示例。您需要添加到 Holder
的两个方法是 __getbuffer__
和 __releasebuffer__
.
2。 Python 拥有记忆;你的 C++ class 持有一个指向 Python 对象的指针
在此版本中,您将内存分配为 numpy 数组(使用 Python C API 接口)。当你的 C++ class 被破坏时,数组的引用计数递减,但是如果 Python 持有对该数组的引用,那么该数组的寿命可能比 C++ class.
长
#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;
}
};
有关如何从 C/C++ 分配 numpy 数组的详细信息,请参阅 the numpy documentation。如果定义 copy/move 构造函数,请注意引用计数。
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 接口到底需要什么类型。
第一个选项可能最难实现,但需要对 C++ 代码进行少量更改。第二个选项可能需要更改您不想进行的 C++ 代码。第三个选项很简单,但意味着 C++ 不再能够访问数据,这可能是一个缺点。
我正在尝试使用 cython
来包装 C++ 库(fastText
,如果相关的话)。 C++ 库 classes 从磁盘加载一个非常大的数组。我的包装器从 C++ 库实例化一个 class 来加载数组,然后使用 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 垃圾收集对我来说很神秘。
如有任何建议,我们将不胜感激。
我可以想到三种明智的做法。我将在下面概述它们(即 none 的代码将是完整的,但希望它会清楚如何完成它)。
1。 C++ 拥有内存; Cython/Python 持有指向 C++ 的共享指针 class
(这看起来是您已经在思考的路线)。
首先创建一个包含共享指针的 Cython class
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
定义缓冲协议。这允许以 numpy 数组和 Cython 内存视图都可以理解的方式直接访问由 cpp_class
分配的内存。因此,它们持有对 Holder
实例的引用,该实例又使 cpp_class
保持活动状态。 (使用np.asarray(holder_instance)
创建一个使用实例内存的numpy数组)
缓冲协议有点复杂,但 Cython 有 fairly extensive documentation,您应该能够在很大程度上复制和粘贴他们的示例。您需要添加到 Holder
的两个方法是 __getbuffer__
和 __releasebuffer__
.
2。 Python 拥有记忆;你的 C++ class 持有一个指向 Python 对象的指针
在此版本中,您将内存分配为 numpy 数组(使用 Python C API 接口)。当你的 C++ class 被破坏时,数组的引用计数递减,但是如果 Python 持有对该数组的引用,那么该数组的寿命可能比 C++ class.
长#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;
}
};
有关如何从 C/C++ 分配 numpy 数组的详细信息,请参阅 the numpy documentation。如果定义 copy/move 构造函数,请注意引用计数。
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 接口到底需要什么类型。
第一个选项可能最难实现,但需要对 C++ 代码进行少量更改。第二个选项可能需要更改您不想进行的 C++ 代码。第三个选项很简单,但意味着 C++ 不再能够访问数据,这可能是一个缺点。