Tob*_*ann 8 c++ python memory-leaks python-3.x tensorflow
以下从C++调用python函数的最小示例在我的系统上有内存泄漏:
script.py:
import tensorflow
def foo(param):
return "something"
Run Code Online (Sandbox Code Playgroud)
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();
}
Run Code Online (Sandbox Code Playgroud)
用.编译
g++ -std=c++11 main.cpp $(python3-config --cflags) $(python3-config --ldflags) -o main
Run Code Online (Sandbox Code Playgroud)
并与valgrind一起运行
valgrind --leak-check=yes ./main
Run Code Online (Sandbox Code Playgroud)
产生以下摘要
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
Run Code Online (Sandbox Code Playgroud)
我使用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"
Run Code Online (Sandbox Code Playgroud)
并重复从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();
}
Run Code Online (Sandbox Code Playgroud)
应用程序的内存消耗在运行时不断增长.
所以我猜我从C++调用python函数的方式存在根本性的错误,但是它是什么?
您的问题中存在两种不同类型的"内存泄漏".
Valgrind告诉你第一种类型的内存泄漏.但是,python模块通常会"泄漏"内存 - 它主要是在加载模块时分配/初始化的一些全局变量.而且因为模块在Python中只加载一次,所以这不是一个大问题.
一个众所周知的例子是numpy PyArray_API:它必须通过初始化_import_array,然后永远不会删除并保留在内存中,直到python解释器关闭.
所以每个设计都是"内存泄漏",你可以争论它是否是一个好的设计,但在一天结束时你无能为力.
我对tensorflow模块没有足够的洞察力来确定发生这种内存泄漏的地方,但我很确定你不应该担心它.
第二个"内存泄漏"更加微妙.
当你比较valgrind输出10^4和10^5循环的迭代时,你可以得到一个领先- 几乎没有差别!然而,峰值内存消耗存在差异.
与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')
Run Code Online (Sandbox Code Playgroud)
并运行它:
>>> python pure.py 1000
Run Code Online (Sandbox Code Playgroud)
我们可以看到以下内容:最后确实存在1000Tersors,这意味着我们创建的对象都没有被处理掉!
如果我们看看链条,它使张量对象保持活着(用它创建objgraph.show_chain),所以我们看到:
有一个tensorflow-Graph-object,其中所有张量都被注册并保持在那里直到会话关闭.
到目前为止,这个理论更为明智:
#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()
Run Code Online (Sandbox Code Playgroud)
也不是这里提出的解决方案
with tf.Graph().as_default(), tf.Session() as sess:
for step in range(int(sys.argv[1])):
foo(" ")
Run Code Online (Sandbox Code Playgroud)
已经适用于当前的tensorflow版本.这可能是一个错误.
简而言之:你的c ++代码中没有任何错误,你不负责任何内存泄漏.事实上,如果你foo一遍又一遍地从纯python脚本中调用函数,你会看到完全相同的内存消耗.
所有创建的Tensors都在Graph-object中注册,并且不会自动发布,您必须通过关闭后端会话来释放它们 - 但是由于当前tensorflow-version 1.4.0中的错误而无法工作.
| 归档时间: |
|
| 查看次数: |
778 次 |
| 最近记录: |