bigywater.tech
Python · 27

tracemalloc:如何定位真正的内存增长来源

用tracemalloc对比两次内存快照,看它是怎么把内存增长精确定位到具体代码行的,跟上一篇只能看到进程总RSS对比。

9418 KiB
增长最多的那一行代码分配的内存
100001个对象
同一行对应的对象数量
行级定位
比进程RSS精确得多的内存排查手段

做的事:对比两个时间点的内存快照

上一篇看到的RSS只是一个总量数字,tracemalloc能告诉你增长发生在哪几行代码。

代码import tracemalloc tracemalloc.start() snap1 = tracemalloc.take_snapshot() leaky = [] def make_leak(): for i in range(50000): leaky.append({"id": i, "data": "x"*50}) make_leak() snap2 = tracemalloc.take_snapshot() top_stats = snap2.compare_to(snap1, 'lineno') for stat in top_stats[:3]: print(stat)

验证:精确定位到了具体的两行代码

不是"总共多用了多少内存",是"哪一行代码造成的"。

结果real_py21_30.py:103: size=9418 KiB (+9418 KiB), count=100001 (+100001), average=96 B real_py21_30.py:102: size=1555 KiB (+1555 KiB), count=49744 (+49744), average=32 B tracemalloc.py:560: size=392 B (+392 B), count=2 (+2), average=196 B current bytes: 11246242 peak bytes: 11246266

第103行是leaky.append({"id": i, "data": "x"*50})这一整行——dict字面量本身的构造分配了9418KB,对应100001个对象(每个循环同时创建了dict本身和"x"*50这个字符串两个对象,所以count接近2×50000);第102行是for i in range(50000)这一行,range对象和循环变量占了1555KB。关键是average这一列:9418KB/100001≈96字节,说明dict这类容器对象即便只装两个键值对,的固定开销就有近百字节,比看起来"只存了一个数字和一个短字符串"要重得多。

验证:current和peak是两个不同的数字

很多人以为"当前内存"就是"用过的最大内存"。

结果里 current bytes: 11246242 和 peak bytes: 11246266 只差了24字节,几乎相等——这是因为本例里leaky列表一直持有着全部5万个字典对象的引用,从头到尾没有释放任何一个,所以"当前占用"和"历史峰值"几乎重合。但如果代码里有过"先分配一大批临时对象,用完就丢弃"的模式,peak会明显大于current——tracemalloc这个peak字段,正是定位"程序运行中某一刻短暂冲高又回落"这类内存尖峰的关键,光看进程当前的RSS或者current字段是看不到这类历史峰值的。

分步演示:内存增长来自哪一行

每一步都是本文上面用tracemalloc跑出来的原始结果。

tracemalloc精确定位到了具体代码行
这一步新验证的统计项

    本文用到的工具

    • 本机 Python 3.14.7
    • tracemalloc:对比两次快照,按代码行定位内存增长来源
    京ICP备2026067502号-1京公网安备11010502063487号