我正在为python编写一个c ++模块。它需要一个图像,进行一些处理并返回一个图像字典。我有记忆泄漏,我无法弄清楚为什么......
我使用opencv-ndarray-conversion在cv::Mat
和numpy.ndarray
我使用Boost.Python将c ++代码转换为python模块。
我使用以下python代码测试c ++模块,同时运行htop
来检查内存使用情况。
import cv2
import this_cpp_module
for i in xrange(100000):
img = cv2.imread('a_640x480x3_image.png')
ret = this_cpp_module.func(img)
#this 'func' is mapping to one of the following c++ functions, using Boost.Python:
# func1, func2 or func3.
1,转换图像不会导致内存泄漏
using namespace boost::python;
PyObject * func1(PyObject *image)
{
NDArrayConverter cvt;
cv::Mat mat;
mat = cvt.toMat(image);
PyObject* ret = cvt.toNDArray(mat);
return ret;
}
2,构造字典并将图像放入其中不会导致内存泄漏
using namespace boost::python;
dict func2(PyObject *image)
{
dict pyDict;
object objImage(handle<>(borrowed(image)));
pyDict[std::string("key")] = objImage;
return pyDict;
}
3,但组合它们会导致内存泄漏(每个循环大约1MB)
dict func3(PyObject *image)
{
return func2(func1(image));
}
我无法理解。一切似乎对我来说都是正确的,但将它们组合在一起只会导致这个问题。
答案 0 :(得分:2)
泄漏是func3()
从未正确处理func1()
返回的临时拥有引用的结果。要解决此问题,func3()
需要执行以下操作之一:
func1()
返回之前,从func3()
返回的拥有引用上显式调用Py_DECREF()
。boost::python::handle
管理func1()
返回的值,因为当handle
被销毁时,它会减少对象的引用计数。例如,func3()
可以写成:
boost::python::dict func3(PyObject* image)
{
// func1() returns an owned reference, so create a handle to keep the
// object alive for at least as long as the handle remains alive. The
// handle will properly dispose of the reference.
boost::python::handle<> handle(func1(image));
return func2(handle.get());
}
有关原始问题的详细信息,当func1()
返回时,返回的对象具有reference count of 1
。从func2()
和func3()
返回后,该对象的引用计数为2
。当从dict
返回的func3()
被销毁时,最初从func1()
返回的对象的引用计数将递减1
,导致泄漏的对象的引用计数为1
。
以下是基于原始代码的完整最小示例:
#include <boost/python.hpp>
PyObject* func1(PyObject*)
{
return PyList_New(0);
}
boost::python::dict func2(PyObject* obj)
{
namespace python = boost::python;
python::dict dict;
python::handle<> handle(python::borrowed(obj));
dict[std::string("key")] = python::object(handle);
return dict;
}
boost::python::dict func3(PyObject* obj)
{
// Fails to properly dispose of the owned reference returned by func1(),
// resulting in a leak.
return func2(func1(obj));
}
boost::python::dict func4(PyObject* obj)
{
// func1() returns an owned reference, so create a handle to keep the
// object alive for at least as long as the handle remains alive. The
// handle will properly dispose of the reference.
boost::python::handle<> handle(func1(obj));
return func2(handle.get());
}
BOOST_PYTHON_MODULE(example)
{
namespace python = boost::python;
python::def("func1", &func1);
python::def("func2", &func2);
python::def("func3", &func3);
python::def("func4", &func4);
}
交互式使用:
>>> from sys import getrefcount
>>> import example
>>> x = example.func1(None)
>>> assert(2 == getrefcount(x)) # refs: x and getrefcount
>>> d = example.func2(x)
>>> assert(3 == getrefcount(x)) # refs: x, d["key"], and getrefcount
>>> d = None
>>> assert(2 == getrefcount(x)) # refs: x and getrefcount
>>> d = example.func3(None)
>>> x = d["key"]
>>> assert(4 == getrefcount(x)) # refs: x, d["key"], getrefcount, and one leak
>>> d = None
>>> assert(3 == getrefcount(x)) # refs: x, getrefcount, and one leak
>>> d = example.func4(None)
>>> x = d["key"]
>>> assert(3 == getrefcount(x)) # refs: x, d["key"], and getrefcount
>>> d = None
>>> assert(2 == getrefcount(x)) # refs: x and getrefcount