Могу ли я заставить 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 не устанавливается.
Неудовлетворительные решения:
Сделайте так, чтобы вызывающая функция get_array_py() отвечала за освобождение памяти. Это супер раздражает; вызывающая сторона должна иметь возможность обрабатывать этот массив numpy так же, как любой другой массив numpy
Скопируйте исходный массив в новый массив 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] Это больше не оболочка, а адаптер.