在Cython中处理C ++数组(使用numpy和pytorch) [英] Handling C++ arrays in Cython (with numpy and pytorch)

查看:196
本文介绍了在Cython中处理C ++数组(使用numpy和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(以及numpypytorch等)会正确共享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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆