tracemalloc — 跟踪内存分配¶
在 3.4 版本加入。
源代码: Lib/tracemalloc.py
tracemalloc 模块是一个调试工具,用于跟踪 Python 分配的内存块。它提供以下信息:
对象分配时的追溯
按文件名和行号统计已分配内存块:总大小、数量和已分配内存块的平均大小
计算两个快照之间的差异以检测内存泄漏
为了跟踪 Python 分配的大多数内存块,应该尽早启动该模块,方法是将 PYTHONTRACEMALLOC 环境变量设置为 1,或者使用 -X tracemalloc 命令行选项。 tracemalloc.start() 函数可以在运行时调用以开始跟踪 Python 内存分配。
默认情况下,已分配内存块的跟踪仅存储最新帧(1 帧)。要在启动时存储 25 帧:将 PYTHONTRACEMALLOC 环境变量设置为 25,或使用 -X tracemalloc=25 命令行选项。
示例¶
显示前 10 个¶
显示分配最多内存的 10 个文件
import tracemalloc
tracemalloc.start()
# ... run your application ...
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
print("[ Top 10 ]")
for stat in top_stats[:10]:
print(stat)
Python 测试套件的输出示例
[ Top 10 ]
<frozen importlib._bootstrap>:716: size=4855 KiB, count=39328, average=126 B
<frozen importlib._bootstrap>:284: size=521 KiB, count=3199, average=167 B
/usr/lib/python3.4/collections/__init__.py:368: size=244 KiB, count=2315, average=108 B
/usr/lib/python3.4/unittest/case.py:381: size=185 KiB, count=779, average=243 B
/usr/lib/python3.4/unittest/case.py:402: size=154 KiB, count=378, average=416 B
/usr/lib/python3.4/abc.py:133: size=88.7 KiB, count=347, average=262 B
<frozen importlib._bootstrap>:1446: size=70.4 KiB, count=911, average=79 B
<frozen importlib._bootstrap>:1454: size=52.0 KiB, count=25, average=2131 B
<string>:5: size=49.7 KiB, count=148, average=344 B
/usr/lib/python3.4/sysconfig.py:411: size=48.0 KiB, count=1, average=48.0 KiB
我们可以看到 Python 从模块加载了 4855 KiB 数据(字节码和常量),并且 collections 模块分配了 244 KiB 来构建 namedtuple 类型。
有关更多选项,请参阅 Snapshot.statistics()。
计算差异¶
拍摄两个快照并显示差异
import tracemalloc
tracemalloc.start()
# ... start your application ...
snapshot1 = tracemalloc.take_snapshot()
# ... call the function leaking memory ...
snapshot2 = tracemalloc.take_snapshot()
top_stats = snapshot2.compare_to(snapshot1, 'lineno')
print("[ Top 10 differences ]")
for stat in top_stats[:10]:
print(stat)
运行 Python 测试套件某些测试之前/之后的输出示例
[ Top 10 differences ]
<frozen importlib._bootstrap>:716: size=8173 KiB (+4428 KiB), count=71332 (+39369), average=117 B
/usr/lib/python3.4/linecache.py:127: size=940 KiB (+940 KiB), count=8106 (+8106), average=119 B
/usr/lib/python3.4/unittest/case.py:571: size=298 KiB (+298 KiB), count=589 (+589), average=519 B
<frozen importlib._bootstrap>:284: size=1005 KiB (+166 KiB), count=7423 (+1526), average=139 B
/usr/lib/python3.4/mimetypes.py:217: size=112 KiB (+112 KiB), count=1334 (+1334), average=86 B
/usr/lib/python3.4/http/server.py:848: size=96.0 KiB (+96.0 KiB), count=1 (+1), average=96.0 KiB
/usr/lib/python3.4/inspect.py:1465: size=83.5 KiB (+83.5 KiB), count=109 (+109), average=784 B
/usr/lib/python3.4/unittest/mock.py:491: size=77.7 KiB (+77.7 KiB), count=143 (+143), average=557 B
/usr/lib/python3.4/urllib/parse.py:476: size=71.8 KiB (+71.8 KiB), count=969 (+969), average=76 B
/usr/lib/python3.4/contextlib.py:38: size=67.2 KiB (+67.2 KiB), count=126 (+126), average=546 B
我们可以看到 Python 已加载 8173 KiB 的模块数据(字节码和常量),这比上次拍摄快照时测试之前加载的 4428 KiB 要多。同样,linecache 模块已缓存 940 KiB 的 Python 源代码以格式化追溯,所有这些都发生在上次快照之后。
如果系统内存不足,可以使用 Snapshot.dump() 方法将快照写入磁盘,以便离线分析快照。然后使用 Snapshot.load() 方法重新加载快照。
获取内存块的追溯¶
显示最大内存块追溯的代码
import tracemalloc
# Store 25 frames
tracemalloc.start(25)
# ... run your application ...
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('traceback')
# pick the biggest memory block
stat = top_stats[0]
print("%s memory blocks: %.1f KiB" % (stat.count, stat.size / 1024))
for line in stat.traceback.format():
print(line)
Python 测试套件的输出示例(追溯限制为 25 帧)
903 memory blocks: 870.1 KiB
File "<frozen importlib._bootstrap>", line 716
File "<frozen importlib._bootstrap>", line 1036
File "<frozen importlib._bootstrap>", line 934
File "<frozen importlib._bootstrap>", line 1068
File "<frozen importlib._bootstrap>", line 619
File "<frozen importlib._bootstrap>", line 1581
File "<frozen importlib._bootstrap>", line 1614
File "/usr/lib/python3.4/doctest.py", line 101
import pdb
File "<frozen importlib._bootstrap>", line 284
File "<frozen importlib._bootstrap>", line 938
File "<frozen importlib._bootstrap>", line 1068
File "<frozen importlib._bootstrap>", line 619
File "<frozen importlib._bootstrap>", line 1581
File "<frozen importlib._bootstrap>", line 1614
File "/usr/lib/python3.4/test/support/__init__.py", line 1728
import doctest
File "/usr/lib/python3.4/test/test_pickletools.py", line 21
support.run_doctest(pickletools)
File "/usr/lib/python3.4/test/regrtest.py", line 1276
test_runner()
File "/usr/lib/python3.4/test/regrtest.py", line 976
display_failure=not verbose)
File "/usr/lib/python3.4/test/regrtest.py", line 761
match_tests=ns.match_tests)
File "/usr/lib/python3.4/test/regrtest.py", line 1563
main()
File "/usr/lib/python3.4/test/__main__.py", line 3
regrtest.main_in_temp_cwd()
File "/usr/lib/python3.4/runpy.py", line 73
exec(code, run_globals)
File "/usr/lib/python3.4/runpy.py", line 160
"__main__", fname, loader, pkg_name)
我们可以看到,大部分内存分配发生在 importlib 模块中,用于从模块加载数据(字节码和常量):870.1 KiB。追溯显示了 importlib 最近加载数据的位置:doctest 模块的 import pdb 行。如果加载新模块,追溯可能会发生变化。
漂亮的前 10 个¶
显示分配最多内存的 10 行代码,并进行漂亮输出,忽略 <frozen importlib._bootstrap> 和 <unknown> 文件
import linecache
import os
import tracemalloc
def display_top(snapshot, key_type='lineno', limit=10):
snapshot = snapshot.filter_traces((
tracemalloc.Filter(False, "<frozen importlib._bootstrap>"),
tracemalloc.Filter(False, "<unknown>"),
))
top_stats = snapshot.statistics(key_type)
print("Top %s lines" % limit)
for index, stat in enumerate(top_stats[:limit], 1):
frame = stat.traceback[0]
print("#%s: %s:%s: %.1f KiB"
% (index, frame.filename, frame.lineno, stat.size / 1024))
line = linecache.getline(frame.filename, frame.lineno).strip()
if line:
print(' %s' % line)
other = top_stats[limit:]
if other:
size = sum(stat.size for stat in other)
print("%s other: %.1f KiB" % (len(other), size / 1024))
total = sum(stat.size for stat in top_stats)
print("Total allocated size: %.1f KiB" % (total / 1024))
tracemalloc.start()
# ... run your application ...
snapshot = tracemalloc.take_snapshot()
display_top(snapshot)
Python 测试套件的输出示例
Top 10 lines
#1: Lib/base64.py:414: 419.8 KiB
_b85chars2 = [(a + b) for a in _b85chars for b in _b85chars]
#2: Lib/base64.py:306: 419.8 KiB
_a85chars2 = [(a + b) for a in _a85chars for b in _a85chars]
#3: collections/__init__.py:368: 293.6 KiB
exec(class_definition, namespace)
#4: Lib/abc.py:133: 115.2 KiB
cls = super().__new__(mcls, name, bases, namespace)
#5: unittest/case.py:574: 103.1 KiB
testMethod()
#6: Lib/linecache.py:127: 95.4 KiB
lines = fp.readlines()
#7: urllib/parse.py:476: 71.8 KiB
for a in _hexdig for b in _hexdig}
#8: <string>:5: 62.0 KiB
#9: Lib/_weakrefset.py:37: 60.0 KiB
self.data = set()
#10: Lib/base64.py:142: 59.8 KiB
_b32tab2 = [a + b for a in _b32tab for b in _b32tab]
6220 other: 3602.8 KiB
Total allocated size: 5303.1 KiB
有关更多选项,请参阅 Snapshot.statistics()。
记录所有跟踪内存块的当前大小和峰值大小¶
以下代码通过创建这些数字的列表,低效地计算两个和,例如 0 + 1 + 2 + ...。此列表会暂时消耗大量内存。我们可以使用 get_traced_memory() 和 reset_peak() 来观察计算和之后的小内存使用量以及计算期间的峰值内存使用量
import tracemalloc
tracemalloc.start()
# Example code: compute a sum with a large temporary list
large_sum = sum(list(range(100000)))
first_size, first_peak = tracemalloc.get_traced_memory()
tracemalloc.reset_peak()
# Example code: compute a sum with a small temporary list
small_sum = sum(list(range(1000)))
second_size, second_peak = tracemalloc.get_traced_memory()
print(f"{first_size=}, {first_peak=}")
print(f"{second_size=}, {second_peak=}")
输出
first_size=664, first_peak=3592984
second_size=804, second_peak=29704
使用 reset_peak() 确保我们能够准确记录 small_sum 计算期间的峰值,即使它远小于自 start() 调用以来内存块的总体峰值大小。如果没有调用 reset_peak(),second_peak 仍然是 large_sum 计算的峰值(即等于 first_peak)。在这种情况下,两个峰值都远高于最终的内存使用量,这表明我们可以进行优化(通过删除对 list 的不必要调用,并编写 sum(range(...)))。
API¶
函数¶
- tracemalloc.get_object_traceback(obj)¶
获取 Python 对象 obj 被分配时的追溯。返回
Traceback实例,如果tracemalloc模块未跟踪内存分配或未跟踪对象的分配,则返回None。另请参阅
gc.get_referrers()和sys.getsizeof()函数。
- tracemalloc.get_traceback_limit()¶
获取跟踪的追溯中存储的最大帧数。
tracemalloc模块必须正在跟踪内存分配才能获取限制,否则会引发异常。限制由
start()函数设置。
- tracemalloc.get_traced_memory()¶
获取
tracemalloc模块跟踪的内存块的当前大小和峰值大小,以元组形式返回:(current: int, peak: int)。
- tracemalloc.reset_peak()¶
将
tracemalloc模块跟踪的内存块的峰值大小设置为当前大小。如果
tracemalloc模块未跟踪内存分配,则不执行任何操作。此函数仅修改记录的峰值大小,不像
clear_traces()那样修改或清除任何跟踪。take_snapshot()调用之前拍摄的快照可以与调用之后拍摄的快照进行有意义的比较。另请参阅
get_traced_memory()。在 3.9 版本中新增。
- tracemalloc.get_tracemalloc_memory()¶
获取
tracemalloc模块用于存储内存块跟踪的内存使用量(以字节为单位)。返回一个int。
- tracemalloc.is_tracing()¶
如果
tracemalloc模块正在跟踪 Python 内存分配,则为True,否则为False。
- tracemalloc.start(nframe: int = 1)¶
开始跟踪 Python 内存分配:在 Python 内存分配器上安装钩子。收集到的跟踪的追溯将被限制为 nframe 帧。默认情况下,内存块的跟踪只存储最近的帧:限制为
1。nframe 必须大于或等于1。您仍然可以通过查看
Traceback.total_nframe属性来读取组成追溯的原始总帧数。存储超过
1帧仅在按'traceback'分组计算统计信息或计算累积统计信息时有用:请参阅Snapshot.compare_to()和Snapshot.statistics()方法。存储更多帧会增加
tracemalloc模块的内存和 CPU 开销。使用get_tracemalloc_memory()函数来测量tracemalloc模块使用的内存量。可以使用
PYTHONTRACEMALLOC环境变量(PYTHONTRACEMALLOC=NFRAME)和-Xtracemalloc=NFRAME命令行选项在启动时开始跟踪。另请参阅
stop()、is_tracing()和get_traceback_limit()函数。
- tracemalloc.stop()¶
停止跟踪 Python 内存分配:卸载 Python 内存分配器上的钩子。还会清除所有以前收集的 Python 分配的内存块的跟踪。
在清除跟踪之前,调用
take_snapshot()函数来拍摄跟踪快照。另请参阅
start()、is_tracing()和clear_traces()函数。
- tracemalloc.take_snapshot()¶
拍摄 Python 分配的内存块的跟踪快照。返回一个新的
Snapshot实例。快照不包括在
tracemalloc模块开始跟踪内存分配之前分配的内存块。跟踪的追溯限制为
get_traceback_limit()帧。使用start()函数的 nframe 参数来存储更多帧。tracemalloc模块必须正在跟踪内存分配才能拍摄快照,请参阅start()函数。另请参阅
get_object_traceback()函数。
DomainFilter¶
Filter¶
- class tracemalloc.Filter(inclusive: bool, filename_pattern: str, lineno: int = None, all_frames: bool = False, domain: int = None)¶
对内存块的跟踪进行过滤。
有关 filename_pattern 的语法,请参阅
fnmatch.fnmatch()函数。'.pyc'文件扩展名将替换为'.py'。示例:
Filter(True, subprocess.__file__)只包括subprocess模块的跟踪Filter(False, tracemalloc.__file__)排除tracemalloc模块的跟踪Filter(False, "<unknown>")排除空追溯
3.5 版本中更改:
'.pyo'文件扩展名不再替换为'.py'。3.6 版本中更改: 添加了
domain属性。- domain¶
内存块的地址空间(
int或None)。tracemalloc 使用域
0来跟踪 Python 进行的内存分配。C 扩展可以使用其他域来跟踪其他资源。
- inclusive¶
如果 inclusive 为
True(包含),则只匹配在文件名与filename_pattern匹配的文件的lineno行号处分配的内存块。如果 inclusive 为
False(排除),则忽略在文件名与filename_pattern匹配的文件的lineno行号处分配的内存块。
- lineno¶
过滤器的行号(
int)。如果 lineno 为None,则过滤器匹配任何行号。
- filename_pattern¶
过滤器的文件名模式(
str)。只读属性。
- all_frames¶
如果 all_frames 为
True,则检查追溯的所有帧。如果 all_frames 为False,则只检查最近的帧。如果追溯限制为
1,则此属性无效。请参阅get_traceback_limit()函数和Snapshot.traceback_limit属性。
Frame¶
Snapshot¶
- class tracemalloc.Snapshot¶
Python 分配的内存块的跟踪快照。
take_snapshot()函数创建一个快照实例。- compare_to(old_snapshot: Snapshot, key_type: str, cumulative: bool = False)¶
与旧快照计算差异。以
StatisticDiff实例的排序列表形式获取统计信息,按 key_type 分组。有关 key_type 和 cumulative 参数,请参阅
Snapshot.statistics()方法。结果按从大到小排序:
StatisticDiff.size_diff的绝对值、StatisticDiff.size、StatisticDiff.count_diff的绝对值、Statistic.count,然后按StatisticDiff.traceback排序。
- filter_traces(filters)¶
创建一个新的
Snapshot实例,其中包含过滤后的traces序列,filters 是DomainFilter和Filter实例的列表。如果 filters 是一个空列表,则返回一个包含跟踪副本的新Snapshot实例。所有包含性过滤器同时应用,如果没有包含性过滤器匹配跟踪,则忽略该跟踪。如果至少有一个排他性过滤器匹配跟踪,则忽略该跟踪。
3.6 版本中更改: 现在 filters 也接受
DomainFilter实例。
- statistics(key_type: str, cumulative: bool = False)¶
获取以
Statistic实例的排序列表形式的统计信息,按 key_type 分组key_type
description
'filename'filename
'lineno'文件名和行号
'traceback'traceback
如果 cumulative 为
True,则累加跟踪的追溯中所有帧(而不仅仅是最近的帧)的内存块大小和数量。累积模式只能与 key_type 等于'filename'和'lineno'一起使用。结果按从大到小排序:
Statistic.size、Statistic.count,然后按Statistic.traceback排序。
- traceback_limit¶
traces的追溯中存储的最大帧数:快照拍摄时get_traceback_limit()的结果。
- traces¶
所有 Python 分配的内存块的跟踪:
Trace实例序列。序列的顺序未定义。使用
Snapshot.statistics()方法获取统计信息的排序列表。
Statistic¶
- class tracemalloc.Statistic¶
内存分配统计。
Snapshot.statistics()返回一个Statistic实例列表。另请参阅
StatisticDiff类。- count¶
内存块的数量(
int)。
- size¶
内存块的总大小(以字节为单位)(
int)。
StatisticDiff¶
- class tracemalloc.StatisticDiff¶
旧快照和新
Snapshot实例之间内存分配的统计差异。Snapshot.compare_to()返回一个StatisticDiff实例列表。另请参阅Statistic类。- count¶
新快照中的内存块数量(
int):如果新快照中内存块已释放,则为0。
- count_diff¶
旧快照和新快照之间内存块数量的差异(
int):如果新快照中内存块已分配,则为0。
- size¶
新快照中内存块的总大小(以字节为单位)(
int):如果新快照中内存块已释放,则为0。
- size_diff¶
旧快照和新快照之间内存块总大小(以字节为单位)的差异(
int):如果新快照中内存块已分配,则为0。
Trace¶
Traceback¶
- class tracemalloc.Traceback¶
Frame实例的序列,按从最旧帧到最新帧的顺序排序。追溯至少包含
1帧。如果tracemalloc模块未能获取帧,则使用文件名"<unknown>"和行号0。拍摄快照时,跟踪的追溯限制为
get_traceback_limit()帧。请参阅take_snapshot()函数。追溯的原始帧数存储在Traceback.total_nframe属性中。这允许判断追溯是否因追溯限制而被截断。Trace.traceback属性是Traceback实例的一个实例。3.7 版本中更改: 帧现在按从最旧到最新的顺序排序,而不是从最新到最旧。
- total_nframe¶
截断前组成追溯的总帧数。如果信息不可用,此属性可以设置为
None。
3.9 版本中更改: 添加了
Traceback.total_nframe属性。- format(limit=None, most_recent_first=False)¶
将追溯格式化为行列表。使用
linecache模块从源代码中检索行。如果设置了 limit,如果 limit 为正,则格式化 limit 个最近的帧。否则,格式化abs(limit)个最旧的帧。如果 most_recent_first 为True,则格式化帧的顺序反转,返回最新的帧而不是最后的帧。类似于
traceback.format_tb()函数,不同之处在于format()不包含换行符。示例
print("Traceback (most recent call first):") for line in traceback: print(line)
输出
Traceback (most recent call first): File "test.py", line 9 obj = Object() File "test.py", line 12 tb = tracemalloc.get_object_traceback(f())