Могу ли я заставить 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 в этом ответе Stackru: /questions/34949373/poluchenie-dannyih-iz-massiva-ctypes-v-numpy/34949389#34949389)

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 не устанавливается.

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

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

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

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

2 ответа

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

PyArray_ENABLEFLAGS(arr, NPY_ARRAY_OWNDATA);

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

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

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


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

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

Я бы хотел экспортировать две функции из моей библиотеки 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] Это больше не оболочка, а адаптер.

Другие вопросы по тегам