首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用tracemalloc模块查找python中的内存泄漏

使用tracemalloc模块查找python中的内存泄漏
EN

Stack Overflow用户
提问于 2020-04-18 20:01:29
回答 1查看 1.5K关注 0票数 1

我有一个使用开源pytorch模型的python脚本,这个代码有一个内存泄漏。我正在使用memory_profiler mprof run --include-children python my_sctipt.py运行此程序,并获得以下图像:

我正在尝试通过系统python模块tracemalloc搜索泄漏的原因:

代码语言:javascript
复制
tracemalloc.start(25)
while True:
    ...
    snap = tracemalloc.take_snapshot()
    domain_filter = tracemalloc.DomainFilter(True, 0)
    snap = snap.filter_traces([domain_filter])
    stats = snap.statistics('lineno', True)
    for stat in stats[:10]:
        print(stat)

如果只查看tracemalloc输出,我将无法识别问题所在。我假设问题出在C扩展中,但我想确保这是真的。我尝试通过DomainFilter更改域名,但仅在0域中有输出。

另外,我不理解tracemalloc.start(frameno)得到的参数的含义,frameno是一些最新的帧,但当我改变它时,什么也不会发生。

接下来我该怎么做才能找到代码中导致内存泄漏的有问题的地方?

期待您的答复。

EN

回答 1

Stack Overflow用户

发布于 2020-04-19 00:17:29

假设您猜测问题出在C扩展中,但您希望确保这是真的,我建议您使用不太特定于python的工具,如https://github.com/vmware/chap,或者至少如果您能够在Linux上运行您的程序。

您需要做的是运行您的脚本(未经检测),并在某个时刻收集一个活动的核心(例如,使用"gcore pid-of your -running-program“)。

拥有该核心后,在chap中打开该核心("chap your- core -file-path"),然后在chap提示符下尝试执行以下命令:

总结可写内容

输出结果如下所示,但是您的数字可能会有很大的不同:

代码语言:javascript
复制
chap> summarize writable
5 ranges take 0x2021000 bytes for use: stack
6 ranges take 0x180000 bytes for use: python arena
1 ranges take 0xe1000 bytes for use: libc malloc main arena pages
4 ranges take 0x84000 bytes for use: libc malloc heap
8 ranges take 0x80000 bytes for use: used by module
1 ranges take 0x31000 bytes for use: libc malloc mmapped allocation
4 ranges take 0x30000 bytes for use: unknown
29 writable ranges use 0x23e7000 (37,646,336) bytes.

摘要中的行是按字节使用的降序给出的,因此您可以遵循该顺序。因此,首先看上面的一个,我们看到它的用法是"stack":

代码语言:javascript
复制
5 ranges take 0x2021000 bytes for use: stack

这个特殊的内核用于一个非常简单的python程序,它启动4个额外的线程,并让所有5个线程休眠。使用多线程python程序可以相当容易地进行大型堆栈分配的原因是,python使用pthread来创建额外的线程,而pthread使用ulimit值作为堆栈大小的默认值。如果您的程序有一个类似的大值,您可以用几种方法之一来更改堆栈大小,包括在父进程中运行"ulimit -s“来更改默认的堆栈大小。要查看哪些值有实际意义,可以在chap提示符下使用以下命令:

代码语言:javascript
复制
chap> describe stacks
Thread 1 uses stack block [0x7fffe22bc000, 7fffe22dd000)
 current sp: 0x7fffe22daa00
Peak stack usage was 0x7798 bytes out of 0x21000 total.

Thread 2 uses stack block [0x7f51ec07c000, 7f51ec87c000)
 current sp: 0x7f51ec87a750
Peak stack usage was 0x2178 bytes out of 0x800000 total.

Thread 3 uses stack block [0x7f51e7800000, 7f51e8000000)
 current sp: 0x7f51e7ffe750
Peak stack usage was 0x2178 bytes out of 0x800000 total.

Thread 4 uses stack block [0x7f51e6fff000, 7f51e77ff000)
 current sp: 0x7f51e77fd750
Peak stack usage was 0x2178 bytes out of 0x800000 total.

Thread 5 uses stack block [0x7f51e67fe000, 7f51e6ffe000)
 current sp: 0x7f51e6ffc750
Peak stack usage was 0x2178 bytes out of 0x800000 total.

5 stacks use 0x2021000 (33,689,600) bytes.

所以你在上面看到的是,其中4个堆栈的大小是8MiB,但很容易低于64KiB。

您的程序可能没有任何堆栈大小问题,但如果有,您可以如上所述修复它们。

继续检查增长原因,查看摘要中的下一行:

代码语言:javascript
复制
6 ranges take 0x180000 bytes for use: python arena

所以python arenas使用的内存排在第二位。它们仅用于特定于python的分配。因此,如果这个值在您的例子中很大,它就证明了您关于C分配是罪魁祸首的理论是错误的,但是稍后您可以做更多的事情来弄清楚这些python分配是如何使用的。

看一下摘要的其余几行,我们看到一些"libc“作为"use”描述的一部分:

代码语言:javascript
复制
1 ranges take 0xe1000 bytes for use: libc malloc main arena pages
4 ranges take 0x84000 bytes for use: libc malloc heap
1 ranges take 0x31000 bytes for use: libc malloc mmapped allocation

请注意,libc负责所有这些内存,但您不能知道内存是否用于非python代码,因为对于超过特定大小阈值(远低于4K)的分配,python通过malloc而不是从某个python arenas获取内存。

因此,假设您已经解决了使用堆栈时可能遇到的所有问题,并且主要使用"python arenas“或"libc malloc”相关的用法。你想知道的下一件事是,这个内存主要是“使用”的(指已分配但从未释放),还是“空闲”(指“已释放但未交还给操作系统”)。

代码语言:javascript
复制
chap> count used
15731 allocations use 0x239388 (2,331,528) bytes.
chap> count free
1563 allocations use 0xb84c8 (754,888) bytes.

因此,在上面的情况下,使用的分配占主导地位,人们应该做的是尝试理解这些使用的分配。免费分配占主导地位的情况要复杂得多,在用户指南中有一些讨论,但在这里需要花费太多的时间来介绍。

因此,让我们暂时假设,在您的案例中,已用分配是增长的主要原因。我们可以找出为什么我们有这么多已使用的分配。

我们可能想知道的第一件事是,是否有任何分配实际上是“泄漏”的,因为它们不再是可访问的。这不包括增长是由于基于容器的增长的情况。

这样做如下所示:

代码语言:javascript
复制
chap> summarize leaked
0 allocations use 0x0 (0) bytes.

因此,对于这个特殊的核心,就像python核心一样,没有任何泄漏。您的数字可能不是零。如果它不是零,但仍然远低于上面报告的与"python“或"libc”相关的内存总量,您可能只是记下泄漏,但继续寻找增长的真正原因。用户指南有一些关于调查泄漏的信息,但它有点稀疏。如果泄漏数量确实足以解释您的增长问题,您应该下一步进行调查,但如果不是,请继续阅读。

现在,您假设了基于容器的增长,以下命令很有用:

代码语言:javascript
复制
chap> redirect on
chap> summarize used
Wrote results to scratch/core.python_5_threads.summarize_used
chap> summarize used /sortby bytes
Wrote results to scratch/core.python_5_threads.summarize_used::sortby:bytes

上面创建了两个文本文件,一个具有按对象计数排序的摘要,另一个具有根据这些对象直接使用的总字节数的摘要。

目前,chap对python的支持非常有限(除了由libc malloc分配的任何对象之外,它还会查找那些python对象,但对于python对象,摘要仅根据模式对python对象进行分类(例如,%SimplePythonObject匹配"int“、"str”、...它不包含其他python对象,并且%ContainerPythonObject匹配tuple、list、dict等内容...它们确实包含对其他python对象的引用)。话虽如此,从摘要中应该很容易看出,已用分配的增长主要是由于python分配的对象还是由本机代码分配的对象。

因此,在这种情况下,如果您特别想找出增长是否是由于本机代码造成的,请查看汇总中的计数,如下所示,所有这些计数都与python相关:

代码语言:javascript
复制
Pattern %SimplePythonObject has 7798 instances taking 0x9e9e8(649,704) bytes.

Pattern %ContainerPythonObject has 7244 instances taking 0xc51a8(807,336) bytes.

Pattern %PyDictKeysObject has 213 instances taking 0xb6730(747,312) bytes.

所以在我举个例子的内核中,python的分配绝对占主导地位。

您还将看到以下内容的一行,该行用于chap尚未识别的分配。您不能对这些是否与python相关做出假设。

代码语言:javascript
复制
Unrecognized allocations have 474 instances taking 0x1e9b8(125,368) bytes.

这将有望回答您的基本问题,即您下一步可以做什么。至少在这一点上,您将理解增长可能是由于C代码还是python代码,并且根据您所找到的内容,chap用户指南应该会帮助您在此基础上更进一步。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/61288749

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档