【问题标题】:Python C extension - memory leaksPython C 扩展 - 内存泄漏
【发布时间】:2019-07-16 12:30:11
【问题描述】:

我对 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 中显式释放数组,则在尝试在test.py 的 for 循环中加载第二个文件时遇到segfault .

知道我做错了什么吗?

【问题讨论】:

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


    【解决方案1】:

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

    但是,一旦完成,您仍然有全局变量 XY 指向现在无效的内存区域。然后在这些无效指针上调用realloc。在这一点上,你已经进入了未定义的行为,所以任何事情都可能发生。


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

    【讨论】:

    • 感谢您的回答。什么是更好的设计?我是否应该在c_load() 中创建numpy 数组对象PyXPyY,然后获取指向它们的PyArray_DATA 的指针并让它们由__load() 函数填充?
    • @charlie_bronx_ 是的——这可能就是我要做的。它相当简单,并保持 Numpy 完全包含内存管理。
    猜你喜欢
    • 1970-01-01
    • 2018-04-08
    • 1970-01-01
    • 2010-09-25
    • 2018-05-30
    • 2011-07-27
    • 2018-05-19
    • 1970-01-01
    • 2014-03-10
    相关资源
    最近更新 更多