以下从 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 Sonya
、g++ 5.4.0
、Python 3.5.2
和 TensorFlow 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-module 没有足够的了解,无法确定发生此类内存泄漏的位置,但我很确定您不必担心。
第二个“内存泄漏”更为隐蔽。
当您比较循环的 10^4
和 10^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("\n\n\n 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
创建),那么我们会看到:
有一个tensorflow-Graph-object,所有张量都在其中注册并留在那里直到session已关闭。
到目前为止的理论,无论多么简洁:
#close session and free resources:
import keras
keras.backend.get_session().close()#free all resources
print("\n\n\n 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
,您会看到完全相同的内存消耗。
所有创建的张量都在一个图形对象中注册并且不会自动释放,您必须通过关闭后端 session 来释放它们 - 但是由于当前 tensorflow-version 1.4.0 中的错误,这不起作用。
关于python - 从 C++ 运行 python 脚本时内存泄漏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48212123/