Я пытаюсь использовать cython
обернуть библиотеку C ++ (fastText
, если это актуально). Классы библиотеки C ++ загружают очень большой массив с диска. Моя оболочка создает экземпляр класса из библиотеки C ++ для загрузки массива, а затем использует cython
представления памяти и numpy.asarray
превратить массив в numpy
массив, затем вызывает torch.from_numpy
создать тензор.
Возникающая проблема заключается в том, как обрабатывать освобождение памяти для массива.
Прямо сейчас я получаю pointer being freed was not allocated
когда программа выходит. Это, как я ожидаю, потому что и код C ++, и numpy
/pytorch
пытаемся управлять тем же фрагментом оперативной памяти.
Я мог бы просто закомментировать деструктор в библиотеке C ++, но мне кажется, что это вызовет у меня другую проблему в будущем.
Как мне подойти к проблеме? Есть ли какая-нибудь документация по передовым практикам о том, как управлять совместным использованием памяти с C ++ и cython
?
Если я изменю библиотеку C ++, чтобы обернуть массив в shared_ptr
, будут cython
(а также numpy
, pytorch
и т. д.) shared_ptr
должным образом?
Я прошу прощения, если вопрос наивный; Сборка мусора Python для меня очень загадочна.
Любой совет приветствуется.
Я могу придумать три разумных способа сделать это. Я обрисую их ниже (т.е. ни один из кодов не будет завершенным, но, надеюсь, будет понятно, как его завершить).
(Похоже, это те строки, о которых вы уже думаете).
Начните с создания класса 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
, Это позволяет прямой доступ к памяти, выделенной cpp_class
таким образом, что могут понять как numy массивы, так и представления памяти Cython. Таким образом, они содержат ссылку на Holder
экземпляр, который в свою очередь держит cpp_class
в живых. (Использование np.asarray(holder_instance)
создать пустой массив, который использует память экземпляра)
Протокол буфера немного сложен, но Cython имеет довольно обширная документация и вы должны в значительной степени быть в состоянии скопировать и вставить их примеры. Два метода, которые нужно добавить в Holder
являются __getbuffer__
а также __releasebuffer__
,
В этой версии вы выделяете память в виде пустого массива (используя интерфейс API Python C). Когда ваш класс 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;
}
};
Увидеть цифровая документация для получения подробной информации о том, как выделить массивы NumPy из C / C ++. Будьте осторожны с подсчетом ссылок, если вы определяете конструкторы копирования / перемещения.
Оболочка Cython выглядит так:
cdef extern from "some_header.hpp":
cdef cppclass cpp_class:
# whatever constructors you want to allow
object get_np_array()
В этой схеме 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
учебный класс захватить выделенную память. Это имеет функцию обратного вызова, которая используется при уничтожении:
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
затем может быть использован с памятью или массивом.
Возможно, вам придется немного повозиться с кастингом из void*
или же char*
с del_cpp_array
— Я не уверен точно, какие типы требует интерфейс Cython.
Первый вариант, вероятно, наиболее эффективен для реализации, но требует немного изменений в коде C ++. Второй вариант может потребовать изменений в коде C ++, которые вы не хотите делать. Третий вариант прост, но означает, что C ++ больше не имеет доступа к данным, что может быть недостатком.
Других решений пока нет …