Обработка массивов C ++ в Cython (с numpy и pytorch)

Я пытаюсь использовать 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 для меня очень загадочна.

Любой совет приветствуется.

3

Решение

Я могу придумать три разумных способа сделать это. Я обрисую их ниже (т.е. ни один из кодов не будет завершенным, но, надеюсь, будет понятно, как его завершить).

(Похоже, это те строки, о которых вы уже думаете).

Начните с создания класса 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 ++ больше не имеет доступа к данным, что может быть недостатком.

5

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]