Python C扩展-内存泄漏

时间:2019-02-22 11:58:47

标签: python memory-leaks python-c-api reference-counting

我对Python比较陌生,这是我第一次尝试编写C扩展。

背景 在我的Python 3.X项目中,我需要加载和解析大型二进制文件(10-100MB)以提取数据以进行进一步处理。二进制内容按帧组织:标头后跟可变数量的数据。由于Python的性能低下,我决定采用C扩展来加快加载速度。

独立的C代码比Python的性能高20到500倍,因此我对此感到非常满意。

问题:当我在同一个Python模块中多次从C扩展调用函数时,内存不断增长。


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

在这里,我正在删除返回的对象,以降低内存使用量。

在阅读了几则帖子(例如thisthisthis)之后,我仍然陷入困境。

我尝试添加/删除PyArray_ENABLEFLAGS设置NPY_ARRAY_OWNDATA标志,但没有任何区别。我尚不清楚NPY_ARRAY_OWNDATA是否暗示C中的free(X)。如果我显式释放C中的数组,则尝试在C中加载第二个文件时遇到了segfaulttest.py中的for循环。

任何关于我在做什么错的主意吗?

1 个答案:

答案 0 :(得分:0)

这看起来像是内存管理灾难。 NPY_ARRAY_OWNDATA应该使它在数据上调用free(或至少是PyArray_free,这不一定是同一件事...)。

但是,一旦完成,您仍然使全局变量XY指向当前无效的内存区域。然后,您对那些无效的指针调用realloc。此时,您很容易陷入未定义的行为,因此任何事情都可能发生。


如果它是一个全局变量,则需要全局管理内存,而不是由Numpy管理。如果内存是由Numpy阵列管理的,那么您需要确保除了通过该Numpy阵列进行存储以外,没有其他存储方式来访问它。其他任何事情都会给您带来麻烦。