27.7. tracemalloc — 跟踪内存分配 — Python 文档
27.7. tracemalloc — 跟踪内存分配
3.4 版中的新功能。
源代码: :source:`Lib/tracemalloc.py`
tracemalloc 模块是一个调试工具,用于跟踪 Python 分配的内存块。 它提供以下信息:
- 回溯分配对象的位置
- 每个文件名和每个行号分配的内存块的统计信息:分配的内存块的总大小、数量和平均大小
- 计算两个快照之间的差异以检测内存泄漏
要跟踪 Python 分配的大部分内存块,应通过将 PYTHONTRACEMALLOC 环境变量设置为 1
或使用 来尽早启动模块-X tracemalloc
命令行选项。 可以在运行时调用 tracemalloc.start() 函数以开始跟踪 Python 内存分配。
默认情况下,已分配内存块的跟踪仅存储最近的帧(1 帧)。 要在启动时存储 25 帧:将 PYTHONTRACEMALLOC 环境变量设置为 25
,或使用 -X tracemalloc=25
命令线选项。
27.7.1. 例子
27.7.1.1. 显示前 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()。
27.7.1.2. 计算差异
拍摄两个快照并显示差异:
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 模块缓存了 Python 源代码的 940 KiB
以格式化回溯,所有这些都是自上一个快照以来的。
如果系统空闲内存很少,可以使用Snapshot.dump()方法将快照写入磁盘,离线分析快照。 然后使用 Snapshot.load() 方法重新加载快照。
27.7.1.3. 获取内存块的回溯
显示最大内存块回溯的代码:
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
行上。 如果加载了新模块,回溯可能会发生变化。
27.7.1.4. 漂亮的上衣
使用漂亮的输出显示分配最多内存的 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]
# replace "/path/to/module/file.py" with "module/file.py"
filename = os.sep.join(frame.filename.split(os.sep)[-2:])
print("#%s: %s:%s: %.1f KiB"
% (index, 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()。
27.7.2. 应用程序接口
27.7.2.1. 职能
- tracemalloc.clear_traces()
清除 Python 分配的内存块的痕迹。
另见 stop()。
- tracemalloc.get_object_traceback(obj)
获取分配 Python 对象 obj 的回溯。 如果 tracemalloc 模块未跟踪内存分配或未跟踪对象的分配,则返回 Traceback 实例或
None
。另见 gc.get_referrers() 和 sys.getsizeof() 函数。
- tracemalloc.get_traceback_limit()
获取存储在跟踪回溯中的最大帧数。
tracemalloc 模块必须跟踪内存分配以获得限制,否则会引发异常。
该限制由 start() 函数设置。
- tracemalloc.get_traced_memory()
- 获取 tracemalloc 模块跟踪的内存块的当前大小和峰值大小作为元组:
(current: int, peak: int)
。
- tracemalloc.get_tracemalloc_memory()
- 获取用于存储内存块跟踪的 tracemalloc 模块的内存使用量(以字节为单位)。 返回 int。
- tracemalloc.is_tracing()
True
如果 tracemalloc 模块正在跟踪 Python 内存分配,则False
否则。
- tracemalloc.start(nframe: int = 1)
开始跟踪 Python 内存分配:在 Python 内存分配器上安装钩子。 收集的跟踪回溯将仅限于 nframe 帧。 默认情况下,内存块的跟踪仅存储最近的帧:限制为
1
。 nframe 必须大于或等于1
。存储超过
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() 函数。
27.7.2.2. 域过滤器
- class tracemalloc.DomainFilter(inclusive: bool, domain: int)
按地址空间(域)过滤内存块的踪迹。
3.6 版中的新功能。
- inclusive
如果 inclusive 是
True
(包括),则匹配分配在地址空间 domain 中的内存块。如果 inclusive 为
False
(排除),则匹配地址空间 domain 中未分配的内存块。
- domain
内存块的地址空间(
int
)。 只读属性。
27.7.2.3. 筛选
- 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
内存块的地址空间(
int
或None
)。
- inclusive
如果 inclusive 是
True
(include),则只匹配在行号 lineno 处名称匹配 filename_pattern 的文件中分配的内存块。如果 inclusive 是
False
(排除),则忽略在行号 lineno 处名称匹配 filename_pattern 的文件中分配的内存块。
- lineno
过滤器的行号 (
int
)。 如果 lineno 是None
,则过滤器匹配任何行号。
- filename_pattern
过滤器的文件名模式 (
str
)。 只读属性。
- all_frames
如果 all_frames 是
True
,则检查回溯的所有帧。 如果 all_frames 是False
,则只检查最近的帧。如果回溯限制为
1
,则此属性无效。 请参阅 get_traceback_limit() 函数和 Snapshot.traceback_limit 属性。
27.7.2.4. 框架
27.7.2.5. 快照
- class tracemalloc.Snapshot
Python 分配的内存块跟踪的快照。
take_snapshot() 函数创建一个快照实例。
- compare_to(old_snapshot: Snapshot, key_type: str, cumulative: bool = False)
计算与旧快照的差异。 获取按 key_type 分组的 StatisticDiff 实例的排序列表形式的统计信息。
有关 key_type 和 cumulative 参数,请参阅 Snapshot.statistics() 方法。
结果从大到小排序:StatisticDiff.size_diff的绝对值,StatisticDiff.size,StatisticDiff.count_diff的绝对值, ]Statistic.count 然后通过 StatisticDiff.traceback。
- dump(filename)
将快照写入文件。
使用 load() 重新加载快照。
- filter_traces(filters)
创建一个新的 Snapshot 实例,其中包含过滤后的 traces 序列,filters 是 DomainFilter 和 Filter 实例的列表. 如果 filters 是一个空列表,则返回一个新的 Snapshot 实例和跟踪的副本。
一次应用所有包含过滤器,如果没有包含过滤器匹配它,则忽略跟踪。 如果至少有一个排他过滤器匹配一条跟踪,则该跟踪将被忽略。
3.6 版更改:DomainFilter 实例现在也被 过滤器 接受。
- classmethod load(filename)
从文件加载快照。
另见 dump()。
- statistics(key_type: str, cumulative: bool = False)
获取按 key_type 分组的 Statistic 实例的排序列表的统计信息:
键类型
描述
'filename'
文件名
'lineno'
文件名和行号
'traceback'
追溯
如果cumulative为
True
,则累加一个trace的traceback的所有帧的内存块的大小和计数,而不仅仅是最近的帧。 累积模式只能用于 key_type 等于'filename'
和'lineno'
。结果从大到小排序:Statistic.size、Statistic.count,然后是Statistic.traceback。
- traceback_limit
traces的回溯中存储的最大帧数:拍摄快照时get_traceback_limit()的结果。
- traces
Python 分配的所有内存块的跟踪:Trace 实例的序列。
该序列具有未定义的顺序。 使用 Snapshot.statistics() 方法获取统计信息的排序列表。
27.7.2.6. 统计
- class tracemalloc.Statistic
内存分配统计。
Snapshot.statistics() 返回 Statistic 实例的列表。
另请参阅 StatisticDiff 类。
- count
内存块数 (
int
)。
- size
以字节为单位的内存块总大小 (
int
)。
- traceback
分配内存块的回溯,Traceback 实例。
27.7.2.7。 统计差异
- class tracemalloc.StatisticDiff
旧的和新的 Snapshot 实例之间内存分配的统计差异。
Snapshot.compare_to() 返回 StatisticDiff 实例的列表。 另请参阅 Statistic 类。
- count
新快照中的内存块数(
int
):0
,如果新快照中的内存块已被释放。
- count_diff
新旧快照内存块数量差异(
int
):0
,如果新快照中已经分配了内存块。
- size
新快照中的内存块总大小(
int
):0
如果新快照中的内存块已被释放。
- size_diff
新旧快照内存块总大小的字节数差异(
int
):0
如果内存块已经在新快照中分配。
- traceback
分配内存块的回溯,Traceback 实例。
27.7.2.8. 痕迹
- class tracemalloc.Trace
内存块的跟踪。
Snapshot.traces 属性是 Trace 实例的序列。
- size
以字节为单位的内存块大小 (
int
)。
- traceback
分配内存块的回溯,Traceback 实例。
27.7.2.9。 追溯
- class tracemalloc.Traceback
Frame 实例的序列从最近的帧到最旧的帧排序。
回溯至少包含
1
帧。 如果tracemalloc
模块无法获取帧,则使用行号0
处的文件名"<unknown>"
。拍摄快照时,跟踪的回溯仅限于 get_traceback_limit() 帧。 请参阅 take_snapshot() 函数。
Trace.traceback 属性是 Traceback 实例的一个实例。
- format(limit=None)
将回溯格式化为带有换行符的行列表。 使用 linecache 模块从源代码中检索行。 如果设置了 limit,则只格式化 limit 最近的帧。
类似于 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())