Расширение Python C — утечки памяти

Я относительно новичок в Python, и это моя первая попытка написать расширение для C.

Предыстория В моем проекте Python 3.X мне нужно загружать и анализировать большие двоичные файлы (10–100 МБ) для извлечения данных для дальнейшей обработки. Двоичный контент организован во фреймы: за заголовками следует переменный объем данных. Из-за низкой производительности в Python я решил использовать расширение C, чтобы ускорить загрузку.

Автономный код C превосходит Python в 20-500 раз, поэтому я им вполне доволен.

Проблема: память продолжает расти, когда я несколько раз вызываю функцию из своего C-расширения в одном и том же модуле Python.


my_c_ext.c

#include <Python.h>
#include <numpy/arrayobject.h>
#include "my_c_ext.h"

static unsigned short *X, *Y;

static PyObject* c_load(PyObject* self, PyObject* args)
{
    char *filename;
    if(!PyArg_ParseTuple(args, "s", &filename))
        return NULL;

    PyObject *PyX, *PyY;

    __load(filename); 

    npy_intp dims[1] = {n_events};

    PyX = PyArray_SimpleNewFromData(1, dims, NPY_UINT16, X);
    PyArray_ENABLEFLAGS((PyArrayObject*)PyX, NPY_ARRAY_OWNDATA);

    PyY = PyArray_SimpleNewFromData(1, dims, NPY_UINT16, Y);
    PyArray_ENABLEFLAGS((PyArrayObject*)PyY, NPY_ARRAY_OWNDATA);

    PyObject *xy = Py_BuildValue("NN", PyX, PyY);


    return xy;
}

...

//More Python C-extension boilerplate (methods, etc..)

...

void __load(char *) {

    // open file, extract frame header and compute new_size
    X = realloc(X, new_size * sizeof(*X));
    Y = realloc(Y, new_size * sizeof(*Y));

    X[i] = ...
    Y[i] = ...

    return;
}

test.py

import my_c_ext as ce

binary_files = ['file1.bin',...,'fileN.bin']

for f in binary_files:
    x,y = ce.c_load(f)
    del x,y

Здесь я удаляю возвращенные объекты в надежде снизить использование памяти.

Прочитав несколько сообщений (например, это, это и это), я все еще застрял.

Я пытался добавить/удалить PyArray_ENABLEFLAGS, установив флаг NPY_ARRAY_OWNDATA, но не заметил никакой разницы. Мне пока не ясно, подразумевает ли NPY_ARRAY_OWNDATA free(X) в C. Если я явно освобождаю массивы в C, я сталкиваюсь с segfault при попытке загрузить второй файл в цикле for в test.py.

Есть идеи, что я делаю неправильно?


person charlie_bronx_    schedule 22.02.2019    source источник


Ответы (1)


Это похоже на катастрофу с управлением памятью. NPY_ARRAY_OWNDATA должен вызывать free для данных (или, по крайней мере, PyArray_free, что не обязательно одно и то же...).

Однако, как только это будет сделано, у вас по-прежнему будут глобальные переменные X и Y, указывающие на уже недействительную область памяти. Затем вы вызываете realloc для этих недопустимых указателей. На данный момент вы находитесь в неопределенном поведении, и поэтому все может случиться.


Если это глобальная переменная, то памятью нужно управлять глобально, а не Numpy. Если память управляется массивом Numpy, вам необходимо убедиться, что у вас нет другого способа доступа к ней, кроме как через этот массив Numpy. Все остальное вызовет у вас проблемы.

person DavidW    schedule 22.02.2019
comment
спасибо за Ваш ответ. Что может быть лучше дизайна? Должен ли я создать объекты массива numpy PyX и PyY внутри c_load(), а затем получить указатель на их PyArray_DATA и заполнить их функцией __load()? - person charlie_bronx_; 25.02.2019
comment
@charlie_bronx_ Да, наверное, я бы так и сделал. Это довольно просто, и управление памятью полностью контролируется Numpy. - person DavidW; 25.02.2019