我写了一个简单的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
}
我希望如果正确处理引用计数,Python垃圾收集会(通常足够)释放输出数组使用的内存(如果它具有相同的名称并重复使用)。所以我用这段代码测试了一些虚拟(但体积很大)的数据:
for i in xrange(200):
a = rand(1000000)
b = mwa.adc3(a)
print i
这里名为“b”的数组被重复使用多次,并且它的内存(由堆中的adc3()借用,预计将返回给系统。我用gnome-system-monitor来检查。与我的期望相反,python拥有的内存增长迅速,只能通过退出程序来释放(我使用IPython)。 为了比较,我尝试了与标准NumPy函数相同的过程,零()和copy():
for i in xrange(1000):
a = np.zeros(10000000)
b = np.copy(a)
print i
如您所见,后一代码不会构建任何内存。 我在标准文档和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;
而不是我的
pout = (double *) malloc(nelem*sizeof(double));
outArray = (PyArrayObject *)
PyArray_SimpleNewFromData(1, dims, NPY_DOUBLE, pout);
/* no matter with or without Py_INCREF(outArray)) */
内存泄漏消失了!该程序现在正常运作。
一个问题:任何人都可以解释为什么PyArray_SimpleNewFromData()不提供正确的引用计数,而PyArray_FromDims()会这样做吗?
非常感谢。
ADDITION。我可能在评论中超出了房间/时间,所以我在这里添加了对Alex的评论。 我试着用这种方式设置OWNDATA标志:
outArray->flags |= OWNDATA;
但我得到了“错误:'OWNDATA'未声明”。 剩下的就在评论中。提前谢谢。
已解决:标志的正确设置是
outArray->flags |= NPY_ARRAY_OWNDATA;
现在可行。
亚历克斯,对不起。答案 0 :(得分:5)
PyArray_SimpleNewFromData
问题不会产生正确的引用PyObject*
。相反,它与您的malloc
一致,分配给pout
,然后从不 free
d。
正如http://docs.scipy.org/doc/numpy/user/c-info.how-to-extend.html处的文档清楚说明,记录PyArray_SimpleNewFromData
:
ndarray
不会拥有自己的数据。这个ndarray
是 取消分配后,指针将不被释放。 ... 如果你想要的话 一旦ndarray
被解除分配,就会释放内存 在返回的OWNDATA
上设置ndarray
标记。
(我强调不)。 IOW,你正在准确地观察“将不会被释放”的行为如此清楚地记录下来,并且如果你想避免这种行为,就不会采取特别推荐的步骤。