Python C 包装器内存泄漏

Posted

技术标签:

【中文标题】Python C 包装器内存泄漏【英文标题】:Python C Wrapper Memory Leak 【发布时间】:2015-02-17 20:47:57 【问题描述】:

我在 python 和 C 方面有一定的经验,但对于将 python 模块编写为 C 函数的包装器是新手。对于一个项目,我需要一个名为“score”的函数,它的运行速度比我在 python 中的运行速度要快得多,所以我用 C 编写了它,实际上只是希望能够从 python 调用它。它接受一个整数的python列表,我希望C函数获取一个整数数组,该数组的长度,然后将一个整数返回给python。这是我当前(工作)的解决方案。

static PyObject *module_score(PyObject *self, PyObject *args) 
    int i, size, value, *gene;
    PyObject *seq, *data;

    /* Parse the input tuple */
    if (!PyArg_ParseTuple(args, "O", &data))
        return NULL;
    seq = PySequence_Fast(data, "expected a sequence");
    size = PySequence_Size(seq);

    gene = (int*) PyMem_Malloc(size * sizeof(int));
    for (i = 0; i < size; i++)
        gene[i] = PyInt_AsLong(PySequence_Fast_GET_ITEM(seq, i));

    /* Call the external C function*/
    value = score(gene, size);

    PyMem_Free(gene);

    /* Build the output tuple */
    PyObject *ret = Py_BuildValue("i", value);
    return ret;

这可行,但似乎以我无法忽视的速度泄漏内存。我通过暂时使 score 函数返回 0 并仍然看到泄漏行为来确保在显示的函数中发生泄漏。我曾认为对 PyMem_Free 的调用应该处理 PyMem_Malloc 的存储,但我目前的猜测是,这个函数中的某些东西在每次调用时都会被分配和保留,因为泄漏行为与调用这个函数的次数成正比。我是否没有正确执行序列到数组的转换,或者我是否可能低效地返回结束值?任何帮助表示赞赏。

【问题讨论】:

它认为,Python 有一个内存池,调用PyMem_Free 不会立即释放指针。它将在内部释放它,以便 Python 可以重用它而无需再次分配它。但是,我不确定。 你在 Linux 上吗?你是如何确定有泄漏的? 我在 Windows 上使用 cygwin,这是一个类似 Linux 的环境。我使用 windows 任务管理器和 top 来查看 python 进程消耗越来越多的内存,然后随机等待打印,以查看内存仅在调用此函数而不是其他 python 函数时才增长。 我会推荐一个memory debugging tool,对于Linux,有一个很棒的叫valgrind,你可以在网上搜索任何类似的Windows 工具。您发布的功能,至少在我看来,一点也不错。 请注意,您确实有一个问题,即您将内存分配为sizeof(int)*size,但您使用longs 而不是ints 填充它。 【参考方案1】:

seq 是一个新的 Python 对象,因此您需要删除该对象。你也应该检查seq 是否为NULL。

类似的东西(未经测试):

static PyObject *module_score(PyObject *self, PyObject *args) 
    int i, size, value, *gene;
    long temp;
    PyObject *seq, *data;

    /* Parse the input tuple */
    if (!PyArg_ParseTuple(args, "O", &data))
        return NULL;
    if (!(seq = PySequence_Fast(data, "expected a sequence")))
        return NULL;

    size = PySequence_Size(seq);

    gene = (int*) PyMem_Malloc(size * sizeof(int));
    for (i = 0; i < size; i++) 
        temp = PyInt_AsLong(PySequence_Fast_GET_ITEM(seq, i));
        if (temp == -1 && PyErr_Occurred()) 
            Py_DECREF(seq);
            PyErr_SetString(PyExc_ValueError, "an integer value is required");
            return NULL;
        
        /* Do whatever you need to verify temp will fit in an int */
        gene[i] = (int*)temp;
    

    /* Call the external C function*/
    value = score(gene, size);

    PyMem_Free(gene);
    Py_DECREF(seq):

    /* Build the output tuple */
    PyObject *ret = Py_BuildValue("i", value);
    return ret;

【讨论】:

对不起,我是新手...如何删除 seq 对象?我在您的示例中没有看到您删除它的任何地方。它不应该被释放,除非它是用 calloc 或 malloc 创建的,对吧? 我更新了答案。要删除 Python 对象,请使用 Py_DECREF()。我还检查了PyInt_AsLong() 的返回值,并在转换为 int 之前添加了一个验证值大小的位置。 谢谢,就是这样!现在不再有内存泄漏。

以上是关于Python C 包装器内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

如何克服 tessnet 内存泄漏?

sh C中的Valgrind内存泄漏检查器

使用 C 扩展 python 时发现内存泄漏

从C ++运行python脚本时内存泄漏

内存泄漏的场景分析和避免方法总结,C语言内存泄漏详解!

如何在源代码中查找内存泄漏