Могу ли я заставить numpy ndarray взять на себя ответственность за свою память?

У меня есть функция C, которая mallocs() и заполняет 2D-массив поплавков. Он "возвращает" этот адрес и размер массива. Подпись

int get_array_c(float** addr, int* nrows, int* ncols);

Я хочу назвать это из Python, поэтому я использую ctypes.

import ctypes
mylib = ctypes.cdll.LoadLibrary('mylib.so')
get_array_c = mylib.get_array_c

Я никогда не выяснял, как указать типы аргументов с помощью ctypes. Я обычно пишу оболочку python для каждой используемой функции C, и удостоверяюсь, что я получаю типы в оболочке. Массив поплавков - это матрица в порядке столбцов, и я хотел бы получить ее как numpy.ndarray. Но он довольно большой, поэтому я хочу использовать память, выделенную функцией C, а не копировать ее. (Я только что нашел этот материал PyBuffer_FromMemory в этом ответе StackOverflow: qaru.site/info/233243/...)

buffer_from_memory = ctypes.pythonapi.PyBuffer_FromMemory
buffer_from_memory.restype = ctypes.py_object

import numpy
def get_array_py():
    nrows = ctypes.c_int()
    ncols = ctypes.c_int()
    addr_ptr = ctypes.POINTER(ctypes.c_float)()
    get_array_c(ctypes.byref(addr_ptr), ctypes.byref(nrows), ctypes.byref(ncols))
    buf = buffer_from_memory(addr_ptr, 4 * nrows * ncols)
    return numpy.ndarray((nrows, ncols), dtype=numpy.float32, order='F',
                         buffer=buf)

Кажется, это дает мне массив с правильными значениями. Но я уверен, что это утечка памяти.

>>> a = get_array_py()
>>> a.flags.owndata
False

Массив не имеет памяти. Справедливо; по умолчанию, когда массив создается из буфера, он не должен. Но в этом случае это должно быть. Когда массив numpy удален, мне бы очень хотелось, чтобы python освободил буферную память для меня. Похоже, если бы я мог заставить owndata к True, это должно сделать это, но owndata не настраивается.

Неудовлетворительные решения:

  • Сделать вызывающим агентом get_array_py() ответственным за освобождение памяти. Это супер раздражает; вызывающий должен иметь возможность обрабатывать этот массив numpy точно так же, как любой другой массив numpy.

  • Скопируйте исходный массив в новый массив numpy (со своей собственной, отдельной памятью) в get_array_py, удалите первый массив и освободите память внутри get_array_py(). Верните копию вместо исходного массива. Это раздражает, потому что это должна быть ненужная копия памяти.

Есть ли способ сделать то, что я хочу? Я не могу сам изменить функцию C, хотя я мог бы добавить в библиотеку еще одну функцию C, если это полезно.

Ответ 1

Я просто наткнулся на этот вопрос, который до сих пор остается проблемой в августе 2013 года. Numpy очень разборчив в отношении флага OWNDATA: его невозможно изменить на уровне Python, поэтому ctypes, скорее всего, не будет в состоянии сделать это. На уровне C-API numpy - и теперь мы говорим о совершенно другом способе создания модулей расширения Python - нужно явно установить флаг с помощью:

PyArray_ENABLEFLAGS(arr, NPY_ARRAY_OWNDATA);

В режиме numpy < 1.7, нужно было быть еще более явным:

((PyArrayObject*)arr)->flags |= NPY_OWNDATA;

Если у вас есть какой-либо контроль над лежащей в основе C-функцией/библиотекой, лучшим решением является передача ему пустого массива numpy соответствующего размера из Python для хранения результата. Основной принцип заключается в том, что распределение памяти всегда должно выполняться на самом высоком уровне, в этом случае на уровне интерпретатора Python.


Как сказал kynan ниже, если вы используете Cython, вам нужно вручную открыть функцию PyArray_ENABLEFLAGS, см. этот пост Force NumPy ndarray, чтобы взять на себя ответственность за свою память в Cython.

Соответствующая документация здесь и здесь.

Ответ 2

Я хотел бы иметь две функции, экспортированные из моей библиотеки C:

int get_array_c_nomalloc(float* addr, int nrows, int ncols); /* Pass addr as argument */
int get_array_c(float **addr, int nrows, int ncols); /* Calls function above */

Затем я написал свою оболочку Python [1] для get_array_c для выделения массива, а затем вызовет get_array_c_nomalloc. Тогда Python действительно владеет памятью. Вы можете интегрировать эту оболочку в свою библиотеку, чтобы ваш пользователь никогда не знал о существовании get_array_c_nomalloc.

[1] На самом деле это не обертка, а адаптер.