27.7. tracemalloc
—跟踪内存分配¶
3.4 新版功能.
源代码: 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从模块加载 4.8 MiB
数据(字节码和常量),并且 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已经加载了 8.2 MiB
的模块数据(字节码和常量),并且这是 4.4 MiB
比在测试之前,当上一个快照被采用时。类似地,linecache
模块已缓存了 940 KiB
的Python源代码以格式化tracebacks,所有这些都是从上一个快照开始的。
如果系统具有很少的可用内存,则可以使用 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 KiB
。回溯是 importlib
最近加载数据的地方:doctest
模块的 import pdb
行。如果加载了新模块,回溯可能会更改。
27.7.1.4. 漂亮的顶部¶
代码显示10行分配最多的内存与一个漂亮的输出,忽略 <frozen importlib._bootstrap>
和 <unknown>
文件:
import linecache
import os
import tracemalloc
def display_top(snapshot, group_by='lineno', limit=10):
snapshot = snapshot.filter_traces((
tracemalloc.Filter(False, "<frozen importlib._bootstrap>"),
tracemalloc.Filter(False, "<unknown>"),
))
top_stats = snapshot.statistics(group_by)
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. API¶
27.7.2.1. 功能¶
-
tracemalloc.
get_object_traceback
(obj)¶ 获取分配Python对象 obj 的traceback。返回一个
Traceback
实例,如果tracemalloc
模块没有跟踪内存分配或者没有跟踪对象的分配,则返回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
)和-X
tracemalloc=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. DomainFilter¶
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
属性。-
domain
¶ 存储器块(
int
或None
)的地址空间。
-
inclusive
¶ 如果 inclusive 是
True
(include),则只匹配在文件中分配的内存块的名称与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
属性。
27.7.2.4. 帧¶
27.7.2.5. 快照¶
-
class
tracemalloc.
Snapshot
¶ 由Python分配的内存块的跟踪快照。
take_snapshot()
函数创建快照实例。-
compare_to
(old_snapshot: Snapshot, group_by: str, cumulative: bool=False)¶ 计算与旧快照的差异。将统计信息作为按 group_by 分组的
StatisticDiff
实例的排序列表。有关 group_by 和 cumulative 参数,请参阅
Snapshot.statistics()
方法。结果从最大到最小排序为:
StatisticDiff.size_diff
的绝对值,StatisticDiff.size
,StatisticDiff.count_diff
的绝对值,Statistic.count
,然后由StatisticDiff.traceback
。
-
filter_traces
(filters)¶ 创建具有过滤的
traces
序列的新Snapshot
实例,filters 是DomainFilter
和Filter
实例的列表。如果 filters 是空列表,则返回带有跟踪副本的新Snapshot
实例。同时应用所有包含的过滤器,如果没有包含的过滤器匹配,则忽略跟踪。如果至少有一个排他过滤器匹配,则会忽略跟踪。
在 3.6 版更改:
DomainFilter
实例现在也被 filters 接受。
-
statistics
(group_by: str, cumulative: bool=False)¶ 将统计信息作为按 group_by 分组的
Statistic
实例的排序列表:通过...分组
描述
'filename'
filename
'lineno'
文件名和行号
'traceback'
追溯
如果 cumulative 是
True
,则累加跟踪的回溯的所有帧的存储器块的大小和计数,而不仅是最近的帧。累积模式只能用于 group_by 等于'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
)。
-
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
。
-
27.7.2.8. 跟踪¶
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())
-