使用PyArray_SimpleNewFromData()创建数组并返回时,Python扩展中的内存泄漏

Ben*_*tch 8 python arrays memory-leaks numpy

我写了一个简单的Python扩展模块来模拟一个3位模数转换器.它应该接受一个浮点数组作为其输入,以返回相同大小的输出数组.输出实际上由量化输入数字组成.这是我的(简化)模块:

static PyObject *adc3(PyObject *self, PyObject *args) {
  PyArrayObject *inArray = NULL, *outArray = NULL;
  double *pinp = NULL, *pout = NULL;
  npy_intp nelem;
  int dims[1], i, j;

  /* Get arguments:  */
  if (!PyArg_ParseTuple(args, "O:adc3", &inArray))
    return NULL;

  nelem = PyArray_DIM(inArray,0); /* size of the input array */
  pout = (double *) malloc(nelem*sizeof(double));
  pinp = (double *) PyArray_DATA(inArray);

  /*   ADC action   */
  for (i = 0; i < nelem; i++) {
    if (pinp[i] >= -0.5) {
    if      (pinp[i] < 0.5)   pout[i] = 0;
    else if (pinp[i] < 1.5)   pout[i] = 1;
    else if (pinp[i] < 2.5)   pout[i] = 2;
    else if (pinp[i] < 3.5)   pout[i] = 3;
    else                      pout[i] = 4;
    }
    else {
    if      (pinp[i] >= -1.5) pout[i] = -1;
    else if (pinp[i] >= -2.5) pout[i] = -2;
    else if (pinp[i] >= -3.5) pout[i] = -3;
    else                      pout[i] = -4;
    }
  }

  dims[0] = nelem;

  outArray = (PyArrayObject *)
               PyArray_SimpleNewFromData(1, dims, NPY_DOUBLE, pout);
  //Py_INCREF(outArray);

  return PyArray_Return(outArray); 
} 

/* ==== methods table ====================== */
static PyMethodDef mwa_methods[] = {
  {"adc", adc, METH_VARARGS, "n-bit Analog-to-Digital Converter (ADC)"},
  {NULL, NULL, 0, NULL}
};

/* ==== Initialize ====================== */
PyMODINIT_FUNC initmwa()  {
    Py_InitModule("mwa", mwa_methods);
    import_array();  // for NumPy
}
Run Code Online (Sandbox Code Playgroud)

我期望如果正确处理引用计数,Python垃圾收集将(通常足够)释放输出数组使用的内存(如果它具有相同的名称并重复使用).所以我用这段代码测试了一些虚拟(但体积很大)的数据:

for i in xrange(200): 
    a = rand(1000000)
    b = mwa.adc3(a)
    print i
Run Code Online (Sandbox Code Playgroud)

这里名为"b"的数组被重复使用多次,其内存(由堆中的adc3()借用,预计将返回给系统.我用gnome-system-monitor来检查.与我的期望相反,python拥有的内存增长迅速,只能通过退出程序来释放(我使用IPython).为了比较,我尝试了与标准NumPy函数相同的过程,零()和copy():

for i in xrange(1000): 
    a = np.zeros(10000000)
    b = np.copy(a)
    print i
Run Code Online (Sandbox Code Playgroud)

如您所见,后一个代码不会构建任何内存.我在标准文档和Web上阅读了很多文本,尝试使用Py_INCREF(outArray)而不是使用它.一切都是徒劳的:问题依然存在.

但是,我在http://wiki.scipy.org/Cookbook/C_Extensions/NumPy_arrays中找到了解决方案.作者提供了一个扩展程序matsq(),它创建一个数组并返回它.当我尝试使用作者建议的调用时:

outArray = (PyArrayObject *) PyArray_FromDims(nd,dims,NPY_DOUBLE);
pout = (double *) outArray->data;
Run Code Online (Sandbox Code Playgroud)

而不是我的

pout = (double *) malloc(nelem*sizeof(double));
outArray = (PyArrayObject *)
            PyArray_SimpleNewFromData(1, dims, NPY_DOUBLE, pout);
/* no matter with or without Py_INCREF(outArray)) */
Run Code Online (Sandbox Code Playgroud)

内存泄漏消失了!该程序现在正常工作.

一个问题:任何人都可以解释为什么PyArray_SimpleNewFromData()不提供正确的引用计数,而PyArray_FromDims()可以吗?

非常感谢你.

加成.我可能在评论中超出了房间/时间,所以我在这里添加了对Alex的评论.我尝试以这种方式设置OWNDATA标志:

outArray->flags |= OWNDATA;
Run Code Online (Sandbox Code Playgroud)

但我得到了"错误:'OWNDATA'未宣布".剩下的就在评论中.先感谢您.


已解决:标志的正确设置是

outArray->flags |= NPY_ARRAY_OWNDATA;
Run Code Online (Sandbox Code Playgroud)

现在它有效.

亚历克斯,抱歉.

Ale*_*lli 6

这个问题是不是PyArray_SimpleNewFromData产生正确引用计数PyObject*.相反,它与你同在malloc,分配到pout那时永远不会 free.

正如http://docs.scipy.org/doc/numpy/user/c-info.how-to-extend.html上的文档清楚地陈述,记录PyArray_SimpleNewFromData:

ndarray不会拥有自己的数据.当这个ndarray被释放,指针将不会被释放....如果您希望在释放后立即释放内存,ndarray则只需OWNDATA在返回的内容上设置标志即可ndarray.

(我强调的不是).IOW,您看到的正是"不会被释放"行为,以便明确记载,并没有采取特别建议你应该要避免步骤所述行为.

  • `PyArray_ENABLEFLAGS(outArray, NPY_ARRAY_OWNDATA);` 是规范的方式,但我猜你的方式(在你的 Q 的编辑中)也有效(在这个版本中——我会使用记录的方式,只是为了将来的证明,但也许我我只是文档的坚持者:-)。所以我现在可以接受吗?-) (2认同)