返回ndarray的字典导致使用boost python的内存泄漏(Returning a dict

2019-10-20 12:50发布

我写的Python一个C ++模块。 它需要一个形象,做了一些处理,并返回图像的字典。 我有内存泄漏问题,我想不通为什么..

我使用OpenCV的-ndarray转换之间进行转换cv::Matnumpy.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));
}

我想不明白。 一切似乎是正确的我,但结合在一起只是导致此问题。

Answer 1:

该泄漏的结果func3()从来没有正确处理返回的临时拥有的引用func1() 。 为了解决这个问题, func3()需要做以下之一:

  • 显式调用Py_DECREF()从返回的拥有的引用func1()从返回之前func3()
  • 管理返回的值func1()boost::python::handle ,因为它会递减对象的引用计数,当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()返回,返回的对象具有的引用计数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


文章来源: Returning a dictionary of ndarray causes memory leaks using boost python