Могу ли я заставить numpy ndarray завладеть его памятью? - PullRequest
11 голосов
/ 03 января 2012

У меня есть функция C, которая mallocs () и заполняет двумерный массив с плавающей точкой. Он «возвращает» этот адрес и размер массива. Подпись

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: https://stackoverflow.com/a/4355701/3691)

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 ]

6 голосов
/ 19 августа 2013

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

PyArray_ENABLEFLAGS(arr, NPY_ARRAY_OWNDATA);

При numpy <1,7, нужно былобыть еще более явным: </p>

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

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


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

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

1 голос
/ 03 января 2012

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

...