从C ++运行python脚本时内存泄漏

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从C ++运行python脚本时内存泄漏相关的知识,希望对你有一定的参考价值。

以下从C ++调用python函数的最小示例在我的系统上有内存泄漏:

script.py

import tensorflow
def foo(param):
    return "something"

main.cpp

#include "python3.5/Python.h"

#include <iostream>
#include <string>

int main()
{
    Py_Initialize();

    PyRun_SimpleString("import sys");
    PyRun_SimpleString("if not hasattr(sys,'argv'): sys.argv = ['']");
    PyRun_SimpleString("sys.path.append('./')");

    PyObject* moduleName = PyUnicode_FromString("script");
    PyObject* pModule = PyImport_Import(moduleName);
    PyObject* fooFunc = PyObject_GetAttrString(pModule, "foo");
    PyObject* param = PyUnicode_FromString("dummy");
    PyObject* args = PyTuple_Pack(1, param);
    PyObject* result = PyObject_CallObject(fooFunc, args);

    Py_CLEAR(result);
    Py_CLEAR(args);
    Py_CLEAR(param);
    Py_CLEAR(fooFunc);
    Py_CLEAR(pModule);
    Py_CLEAR(moduleName);

    Py_Finalize();
}

用。编译

g++ -std=c++11 main.cpp $(python3-config --cflags) $(python3-config --ldflags) -o main

并与valgrind一起运行

valgrind --leak-check=yes ./main

产生以下摘要

LEAK SUMMARY:
==24155==    definitely lost: 161,840 bytes in 103 blocks
==24155==    indirectly lost: 33 bytes in 2 blocks
==24155==      possibly lost: 184,791 bytes in 132 blocks
==24155==    still reachable: 14,067,324 bytes in 130,118 blocks
==24155==                       of which reachable via heuristic:
==24155==                         stdstring          : 2,273,096 bytes in 43,865 blocks
==24155==         suppressed: 0 bytes in 0 blocks

我正在使用Linux Mint 18.2 Sonyag++ 5.4.0Python 3.5.2TensorFlow 1.4.1

删除import tensorflow使泄漏消失。这是TensorFlow中的错误还是我做错了什么? (我希望后者是真的。)


另外,当我在Python中创建Keras层时

#script.py
from keras.layers import Input
def foo(param):
    a = Input(shape=(32,))
    return "str"

并重复从C ++运行Python调用

//main.cpp

#include "python3.5/Python.h"

#include <iostream>
#include <string>

int main()
{
    Py_Initialize();

    PyRun_SimpleString("import sys");
    PyRun_SimpleString("if not hasattr(sys,'argv'): sys.argv = ['']");
    PyRun_SimpleString("sys.path.append('./')");

    PyObject* moduleName = PyUnicode_FromString("script");
    PyObject* pModule = PyImport_Import(moduleName);

    for (int i = 0; i < 10000000; ++i)
    {
        std::cout << i << std::endl;
        PyObject* fooFunc = PyObject_GetAttrString(pModule, "foo");
        PyObject* param = PyUnicode_FromString("dummy");
        PyObject* args = PyTuple_Pack(1, param);
        PyObject* result = PyObject_CallObject(fooFunc, args);

        Py_CLEAR(result);
        Py_CLEAR(args);
        Py_CLEAR(param);
        Py_CLEAR(fooFunc);
    }

    Py_CLEAR(pModule);
    Py_CLEAR(moduleName);

    Py_Finalize();
}

应用程序的内存消耗在运行时不断增长。

所以我猜我从C ++调用python函数的方式存在根本性的错误,但是它是什么?

答案

您的问题中存在两种不同类型的“内存泄漏”。

Valgrind告诉你第一种类型的内存泄漏。但是,python模块通常会“泄漏”内存 - 它主要是在加载模块时分配/初始化的一些全局变量。而且因为模块在Python中只加载一次,所以这不是一个大问题。

一个众所周知的例子是numpy的PyArray_API:它必须通过_import_array初始化,然后永远不会删除并保留在内存中,直到python解释器关闭。

所以每个设计都是“内存泄漏”,你可以争论它是否是一个好的设计,但在一天结束时你无能为力。

我对tensorflow模块没有足够的洞察力来确定发生这种内存泄漏的地方,但我很确定你不应该担心它。


第二个“内存泄漏”更加微妙。

当你比较10^410^5循环迭代的valgrind输出时,你可以得到一个领先 - 几乎没有差别!然而,峰值内存消耗存在差异。

与C ++不同,Python有一个垃圾收集器 - 所以你无法知道对象何时被破坏。 CPython使用引用计数,因此当引用计数为0时,对象将被销毁。但是,当有一个引用循环时(例如,对象A持有对象B的引用而对象B持有对象B的引用),它不是那么简单:垃圾收集器需要遍历所有对象以找到不再使用的对象周期。

有人可能会认为,keras.layers.Input在某个地方有这样一个循环(这是真的),但这不是这种“内存泄漏”的原因,对于纯python也可以观察到。

我们使用objgraph-package检查引用,让我们运行以下python脚本:

#pure.py
from keras.layers import Input
import gc
import sys
import objgraph


def foo(param):
    a = Input(shape=(1280,))
    return "str"

###  MAIN :

print("Counts at the beginning:")
objgraph.show_most_common_types()
objgraph.show_growth(limit=7) 

for i in range(int(sys.argv[1])):
   foo(" ")

gc.collect()# just to be sure

print("


 Counts at the end")
objgraph.show_most_common_types()
objgraph.show_growth(limit=7)

import random
objgraph.show_chain(
   objgraph.find_backref_chain(
        random.choice(objgraph.by_type('Tensor')), #take some random tensor
         objgraph.is_proper_module),
    filename='chain.png') 

并运行它:

>>> python pure.py 1000

我们可以看到以下内容:最后确实有1000 Tersors,这意味着我们创建的对象都没有被处理掉!

如果我们看看链条,它保持张量对象的存活(用objgraph.show_chain创建),所以我们看到:

enter image description here

有一个tensorflow-Graph-object,其中所有张量都被注册并保持在那里直到session关闭。

到目前为止,这个理论更为明智:

#close session and free resources:
import keras
keras.backend.get_session().close()#free all resources

print("


 Counts after session.close():")
objgraph.show_most_common_types()

也不是here提出的解决方案:

with tf.Graph().as_default(), tf.Session() as sess:
   for step in range(int(sys.argv[1])):
     foo(" ")

已经适用于当前的tensorflow版本。这可能是一个bug


简而言之:你的c ++代码中没有任何错误,你不负责任何内存泄漏。事实上,如果你一遍又一遍地从纯python脚本中调用函数foo,你会看到完全相同的内存消耗。

所有创建的Tensors都在Graph-object中注册,并且不会自动发布,您必须通过关闭后端会话来释放它们 - 但是由于当前tensorflow-version 1.4.0中的错误而无法工作。

以上是关于从C ++运行python脚本时内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

从 java 代码运行时,Python 脚本找不到依赖项

运行 python 脚本运行 shell 文件时退出代码 191

Python C 包装器内存泄漏

从 AJAX 或 JQuery 运行 Python 脚本

从 c++ 代码运行 python 脚本并在 c++ 中使用 pythons 输出

python特性+python安装测试