返回 ndarray 的字典会导致使用 boost python 的内存泄漏

Posted

技术标签:

【中文标题】返回 ndarray 的字典会导致使用 boost python 的内存泄漏【英文标题】:Returning a dictionary of ndarray causes memory leaks using boost python 【发布时间】:2014-07-21 21:43:38 【问题描述】:

我正在为 python 编写一个 c++ 模块。它需要一张图像,进行一些处理并返回一个图像字典。我有内存泄漏,我不知道为什么..

我使用opencv-ndarray-conversion 在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));

我想不通。一切对我来说似乎都是正确的,但将它们组合在一起只会导致这个问题。

【问题讨论】:

能否添加func的源代码,可能与内存泄漏有关。 @AndrewJohnson func 映射到 c++ 函数之一:func1func2func3 【参考方案1】:

泄漏是func3() 从未正确处理func1() 返回的临时拥有的引用的结果。要解决此问题,func3() 需要执行以下操作之一:

在从func3() 返回之前,对从func1() 返回的拥有引用显式调用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。当从func3()返回的dict被销毁时,最初从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

【讨论】:

以上是关于返回 ndarray 的字典会导致使用 boost python 的内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

包含 Boost C++ 标头会导致 dlopen() 返回错误:_ZTVN10__cxxabiv117__class_type_infoE

如何将 cupy.ndarray 转换为标量?

添加嵌套字典会导致 JSONSerialization 返回 nil

Boost/Python 有 make_array 方法吗?

在元组的ndarray中查找元组并返回搜索到的元组的索引

Boost.python 和 OMP