Python的Cextension内存泄漏

2024-05-19 00:00:59 发布

您现在位置:Python中文网/ 问答频道 /正文

<>这是我第一次为Python写一个C扩展,你可以看到我的一个丑陋的和可能是非常低效的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;
}

Tags: fordatacolitemkernelintrowpyobject
1条回答
网友
1楼 · 发布于 2024-05-19 00:00:59

泄漏源于:

PyList_Append(ret_array, PyFloat_FromDouble(channel_sum));

PyFloat_FromDouble创建一个新的引用,PyList_Append获得引用的共享所有权(它不会窃取/使用引用)。当使用PyList_Append并且希望list获得自己引用的所有权时,必须在附加后显式释放引用,例如(省略错误检查):

^{pr2}$

另一种解决方案(如果合适的话更快)是将list预先分配到正确的大小,并用PyList_SetItem/PyList_SET_ITEM填充条目,这两个条目都是一个引用,而不是增加引用计数。一般来说,那些没有明确提到引用窃取的api是不会的,而且您需要控制自己的引用计数。在

注意,在内存方面,单个PyFloat比C doubles(它们包装)要贵很多;在64位系统中,list中的每个PyFloat消耗32个字节(8个用于list中的指针,24个用于PyFloat本身),而原始Cdouble则消耗8个字节。在

您可能需要考虑使用Python's ^{} module(创建一个大小/类型正确的array,使用缓冲协议生成一个C级视图,然后填充缓冲区);代码将稍微复杂一些,但是内存使用量将下降4倍。numpy类型将提供同样的优势(并且结果可能会更灵活地使用)。在

相关问题 更多 >

    热门问题