python - Python C 扩展中的内存泄漏

标签 python c python-c-api

这是我第一次为 python 编写 C 扩展,你可以看到我丑陋且可能 super 低效的卷积 C++ 实现。我的内存管理有问题。每次我在 python 中调用这个函数时,它都会消耗大约 500MB 的内存(对于大小为 100x112x112x3 的批处理和大小为 3x3x3x64 的内核),并且之后不会释放它。即使这不是类方法,我是否必须注意引用计数?或者我是否必须在代码中的某个位置手动释放内存?请注意,为了更好地概览,我排除了所有错误检查。谢谢。

PyObject* conv2d(PyObject*, PyObject* args)

{
    PyObject* data;
    PyObject* shape;
    PyObject* kernel;
    PyObject* k_shape;
    int stride;

    PyArg_ParseTuple(args, "OOOOi", &data, &shape, &kernel, &k_shape, &stride);

    Py_ssize_t dims = PyTuple_Size(shape);
    Py_ssize_t kernel_dims = PyTuple_Size(k_shape);

    int shape_c[3];
    int k_shape_c[4];

    for (int i = 0; i < kernel_dims; i++)
    {
        if (i < dims)
        {
            shape_c[i] = PyLong_AsLong(PyTuple_GetItem(shape, i));
        }
        k_shape_c[i] = PyLong_AsLong(PyTuple_GetItem(k_shape, i));
    }

    PyObject* data_item, kernel_item;
    PyObject* ret_array = PyList_New(0);
    double conv_val, channel_sum;

    for (int oc = 0; oc < k_shape_c[3]; oc++)
    {
        for (int row = 0; row < shape_c[0]; row += stride)
        {
            for (int col = 0; col < shape_c[1]; col += stride)
            {
                channel_sum = 0;
                for (int ic = 0; ic < shape_c[2]; ic++)
                {
                    conv_val = 0;
                    for (int k_row = 0; k_row < k_shape_c[0]; k_row++)
                    {
                        for (int k_col = 0; k_col < k_shape_c[1]; k_col++)
                        {
                            data_item = PyList_GetItem(data, row + k_row);
                            if (!data_item)
                            {
                                PyErr_Format(PyExc_IndexError, "Index out of bounds");
                                return NULL;
                            }
                            data_item = PyList_GetItem(data_item, col + k_col);
                            data_item = PyList_GetItem(data_item, ic);
                            kernel_item = PyList_GetItem(kernel, k_row);
                            kernel_item = PyList_GetItem(kernel_item, k_col);
                            kernel_item = PyList_GetItem(kernel_item, ic);
                            kernel_item = PyList_GetItem(kernel_item, oc);
                            conv_val += PyFloat_AsDouble(data_item) * PyFloat_AsDouble(kernel_item);
                        }
                    }
                    channel_sum += conv_val;
                }
                PyList_Append(ret_array, PyFloat_FromDouble(channel_sum));
            }
        }
    }
    return ret_array;
}

最佳答案

泄漏来自:

PyList_Append(ret_array, PyFloat_FromDouble(channel_sum));

PyFloat_FromDouble创建一个新引用 PyList_Append获取引用的共享所有权(它不会窃取/消耗引用)。使用时PyList_Append你想要list要获得您自己的引用的所有权,您必须在附加后明确释放您的引用,例如(省略错误检查):

PyObject *pychannel_sum = PyFloat_FromDouble(channel_sum);
PyList_Append(ret_array, pychannel_sum);
Py_DECREF(pychannel_sum);

替代方案(如果合适,速度更快)解决方案是预分配 list到正确的大小,并填写条目 PyList_SetItem/PyList_SET_ITEM ,两者都窃取引用,而不是增加引用计数。一般来说,没有明确提及引用窃取的 API 不会,并且您需要监管自己的引用计数。

注意内存方面,个人PyFloat s 比 C 贵很多 double s(它们包裹的);在 64 位系统上,每个 PyFloatlist占用 32 个字节(list 中的指针占用 8 个字节,PyFloat 本身占用 24 个字节),而原始 C double 占用 8 个字节。 .

您可能想考虑使用Python's array module (创建一个正确大小/类型的 array,使用缓冲区协议(protocol)创建它的 C 级 View ,然后填充缓冲区);代码会稍微复杂一些,但内存使用量会下降 4 倍。 numpy类型将提供相同的优势(并且结果可以更灵活地使用)。

关于python - Python C 扩展中的内存泄漏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57143041/

相关文章:

通过 jinja 定义时,JavaScript 数组对象不适用于 Highcharts (Django 2.0)

python - 为什么我不能打印堆队列?

c - 为什么错误的结构会导致段错误

Python 对象未使用 C API 完全初始化

python - 使用扩展在 Python 中创建新类型

python - 如何将 % 添加到 numpy 数组中的每个值?

c - 当用户按 Enter 时结束输入 scanf

c++ - 在 C++ 中包装 C,仅用于 try/catch

python - PyEval_CallObject 偶尔会在循环中失败

python - 字符串与特定变量字符的组合