在Cython中处理C ++数组(使用numpy和pytorch) [英] Handling C++ arrays in Cython (with numpy and pytorch)
问题描述
我正在尝试使用cython
包装一个C ++库(fastText
,如果相关). C ++库类从磁盘加载非常大的数组.我的包装器实例化了C ++库中的一个类以加载数组,然后使用cython
内存视图和numpy.asarray
将数组转换为numpy
数组,然后调用torch.from_numpy
创建张量.
I am trying to use cython
to wrap a C++ library (fastText
, if its relevant). The C++ library classes load a very large array from disk. My wrapper instantiates a class from the C++ library to load the array, then uses cython
memory views and numpy.asarray
to turn the array into a numpy
array, then calls torch.from_numpy
to create a tensor.
出现的问题是如何处理为数组分配内存的问题.
The problem arising is how to handle deallocating the memory for the array.
现在,当程序退出时,我得到pointer being freed was not allocated
.我希望这是因为C ++代码和numpy
/pytorch
都试图管理相同的RAM块.
Right now, I get pointer being freed was not allocated
when the program exits. This is, I expect, because both the C++ code and numpy
/pytorch
are trying to manage the same chunk of RAM.
我可以简单地注释掉C ++库中的析构函数,但这似乎会在以后给我带来另一个问题.
I could simply comment out the destructor in the C++ library, but that feels like its going to cause me a different problem down the road.
我应该如何处理该问题?关于如何处理C ++和cython
的内存共享,是否有某种最佳实践文档?
How should I approach the issue? Is there any kind of best practices documentation somewhere on how to handle memory sharing with C++ and cython
?
如果我修改C ++库以将数组包装在shared_ptr
中,cython
(以及numpy
,pytorch
等)会正确共享shared_ptr
吗?
If I modify the C++ library to wrap the array in a shared_ptr
, will cython
(and numpy
, pytorch
, etc.) share the shared_ptr
properly?
如果问题太幼稚,我深表歉意. Python垃圾回收对我来说很神秘.
I apologize if the question is naive; Python garbage collection is very mysterious to me.
任何建议都值得赞赏.
Any advice is appreciated.
推荐答案
我可以想到三种明智的方法.我将在下面概述它们(即,所有代码都不会完整,但是希望可以清楚地知道如何完成).
I can think of three sensible ways of doing it. I'll outline them below (i.e. none of the code will be complete but hopefully it will be clear how to finish it).
(这似乎是您已经在思考的路线).
(This is looks to be the lines you're already thinking along).
首先创建一个保存共享指针的Cython类
Start by creating a Cython class that holds a shared pointer
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 memoryview都可以理解的方式直接访问cpp_class
分配的内存.因此,它们持有对Holder
实例的引用,这又使cpp_class
保持活动状态. (使用np.asarray(holder_instance)
创建一个使用实例内存的numpy数组)
You then need to define the buffer protocol for Holder
. This allows direct access to the memory allocated by cpp_class
in a way that both numpy arrays and Cython memoryviews can understand. Thus they hold a reference to a Holder
instance, which in turn keeps a cpp_class
alive. (Use np.asarray(holder_instance)
to create a numpy array that uses the instance's memory)
涉及到缓冲协议,但是Cython拥有相当详尽的文档,那么您应该可以复制并粘贴他们的示例.您需要添加到Holder
的两个方法是__getbuffer__
和__releasebuffer__
.
The buffer protocol is a little involved but Cython has fairly extensive documentation and you should largely be able to copy and paste their examples. The two methods you need to add to Holder
are __getbuffer__
and __releasebuffer__
.
在此版本中,您将内存分配为numpy数组(使用Python C API接口).当您的C ++类以递减的方式进行销毁时,但是,如果Python持有对该数组的引用,则该数组的寿命将超过C ++类.
In this version you allocate the memory as a numpy array (using the Python C API interface). When your C++ class is destructed in decrements the reference count of the array, however if Python holds references to that array then the array can outlive the 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;
}
};
请参见 numpy文档有关如何从C/C ++分配numpy数组的详细信息.如果定义了复制/移动构造函数,请小心参考计数.
See the numpy documentation for details of the how to allocate numpy arrays from C/C++. Be careful of reference counting if you define copy/move constructors.
Cython包装器如下所示:
The Cython wrapper then looks like:
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 ++将不再有权访问数据.
3. C++ transfers ownership of the data to Python/Cython
In this scheme C++ allocates the array, but Cython/Python is responsible for deallocating it. Once ownership is transferred C++ no longer has access to the data.
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
类捕获分配的内存.它具有用于销毁的回调函数:
You then use the cython.view.array
class to capture the allocated memory. This has a callback function which is used on destruction:
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数组一起使用.
arr
can then be used with a memoryview or a numpy array.
从void*
或char*
到del_cpp_array
的转换可能会有些混乱-我不确定Cython接口需要什么类型.
You may have to mess about a bit with casting from void*
or char*
with del_cpp_array
- I'm not sure exactly what types the Cython interface requires.
第一个选项可能是大多数实现的工作,但对C ++代码的更改很少.第二个选项可能需要更改您不想进行的C ++代码.第三个选项很简单,但是意味着C ++不再有权访问数据,这可能是不利的.
The first option is probably most work to implement but requires few changes to the C++ code. The second option may require changes to your C++ code that you don't want to make. The third option is simple but means that C++ no longer has access to the data, which might be a disadvantage.
这篇关于在Cython中处理C ++数组(使用numpy和pytorch)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!