– 跟踪内存分配 – 调试和分析(Python教程)(参考资料)
tracemalloc– 跟踪内存分配
版本3.4.
源代码:LIB / tracemalloc.py
tracemalloc模块是一个用于跟踪由Python分配的内存块的调试工具。它提供以下信息:
要跟踪Python分配的大多数内存块,应通过设置PYTHONTRACEMALLOC环境变量为1,或者使用-X tracemalloc命令行选项。tracemalloc.start()函数可以在运行时调用tostart跟踪Python内存分配.
默认情况下,分配的内存块的跟踪仅存储最近的帧(1帧)。在启动时存储25帧:将PYTHONTRACEMALLOC环境变量设置为25,或使用-X tracemalloc=25命令行选项.
例子
显示前10个
显示分配最多内存的10个文件:
import tracemalloctracemalloc.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数据(字节码和常量)frommodules和collections模块分配244 KiB构建namedtuple types.
查看Snapshot.statistics()更多选项.
计算差异
拍摄两张快照并显示差异:
import tracemalloctracemalloc.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 framestracemalloc.start(25)# ... run your application ...snapshot = tracemalloc.take_snapshot()top_stats = snapshot.statistics("traceback")# pick the biggest memory blockstat = 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模块中分配来自modules:870.1 KiB的toload数据(字节码和常量)。回溯是importlib最近加载的数据:import pdb模块的doctest行。如果加载了一个新模块,回溯可能会改变.
Pretty top
代码显示10行分配最大内存的漂亮输出,忽略<frozen importlib._bootstrap>和<unknown> files:
import linecacheimport osimport tracemallocdef 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 KiBTotal allocated size: 5303.1 KiB
参见Snapshot.statistics()了解更多选项.
API
函数
tracemalloc.clear_traces()-
清除Python分配的内存块的痕迹
参见
stop().
tracemalloc.get_object_traceback(obj)-
获取分配了Python对象obj的回溯。返回
Traceback实例,或None如果tracemalloc模块没有跟踪内存分配或没有跟踪对象的分配.参见
gc.get_referrers()和sys.getsizeof()functions.
tracemalloc.get_traceback_limit()-
获取跟踪回溯中存储的最大帧数
tracemalloc模块必须跟踪内存分配以达到限制,否则会引发异常.限制由
start()功能设定
tracemalloc.get_traced_memory()-
获取当前大小和记忆的峰值大小
tracemalloc模块跟踪的块作为元组:(current: int, peak: int).
tracemalloc.get_tracemalloc_memory()-
获取
tracemalloc模块的内存使用量(以字节为单位)用于存储内存块的痕迹。如果int.
tracemalloc.is_tracing模块正在跟踪Python内存分配,则返回(-
True)tracemalloc,否则False否则.参见
start()和stop()functions.
tracemalloc.start(nframe: int=1)-
开始追踪Python内存分配:在Python memoryallocators上安装钩子。收集的跟踪回溯将限制为nframe帧。默认情况下,内存块的跟踪仅存储最新的帧:限制为
1. nframe必须大于或等于1.存储超过
1frame仅对计算由"traceback"或者计算累积统计数据:请参阅Snapshot.compare_to()和Snapshot.statistics()methods.调整更多帧会增加
tracemalloc模块的内存和CPU开销。使用get_tracemalloc_memory()函数来测量tracemalloc模块使用了多少内存PYTHONTRACEMALLOC环境变量(PYTHONTRACEMALLOC=NFRAME)和-Xtracemalloc=NFRAME命令行选项可用于在启动时开始跟踪.参见
stop(),is_tracing()和get_traceback_limit()functions.
tracemalloc.stop( )-
停止跟踪Python内存分配:在Python内存分配器上卸载挂钩。同时清除所有以前收集的由Python分配的内存块的痕迹.
Call
take_snapshot()在清理之前拍摄痕迹的快照.参见
start(),is_tracing()和clear_traces()functions.
DomainFilter
- class
tracemalloc.DomainFilter(inclusive: bool, domain: int) -
通过地址空间(域)过滤内存块的痕迹.
版本3.6.
inclusive-
如果inclusive是
True(include),匹配地址空间中分配的内存块domain.如果inclusive是
False(不包括),匹配地址空间中未分配的内存块domain.
domain-
地址记忆块的空间(
int)。只读属性.
过滤
- class
tracemalloc.Filter(inclusive: bool, filename_pattern: str, lineno: int=None, all_frames: bool=False, domain: int=None) -
过滤内存块的痕迹.
见
fnmatch.fnmatch()函数为filename_pattern。".pyc"文件扩展名用".py".例子:
Filter(True, subprocess.__file__)只包括subprocess模块Filter(False, tracemalloc.__file__)不包括tracemalloc模块Filter(False, "<unknown>")排除空回溯
在版本3.5中更改:
".pyo"文件扩展名不再替换为".py".在版本3.6中更改:添加了
domainattribute.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属性
帧
- class
tracemalloc.Frame -
追溯框架
Tracebackclass是Frameinstances.filename-
文件名 (
str).
lineno-
电话号码 (
int).
快照
- class
tracemalloc.Snapshot -
Python分配的内存块跟踪快照.
take_snapshot()function创建快照实例.compare_to(old_snapshot: Snapshot, key_type: str, cumulative: bool=False)-
用旧快照计算差异。获取统计数据作为
StatisticDiff实例的分类列表,按key_type.分组查看
Snapshot.statistics()方法key_type和cumulativeparameters.结果从最大到最小排序为:
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实例现在也被filters.
- classmethod
load(filename) -
从文件加载快照
另见
dump().
statistics(key_type: str, cumulative: bool=False)-
获取统计数据作为
Statistic实例分组key_type:key_type的 描述 "filename"文件名 "lineno"文件名和行号 "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实例的列表也见
StatisticDiffclass.count-
内存块的数量(
int).
size-
内存块的总大小以字节为单位(
int).
traceback-
分配内存块的回溯,
Tracebackinstance.
StatisticDiff
- class
tracemalloc.StatisticDiff -
新老内存分配的统计差异
Snapshotinstance.Snapshot.compare_to()返回StatisticDiff实例。另见Statisticclass.count-
新快照中的内存块数量(
int):0如果新快照中已释放内存块.
count_diff-
旧快照和新闻快照之间的内存块数量差异(
int):0如果已在新快照中分配了内存块
size-
新快照中内存块的总大小(以字节为单位)(
int):0如果新快照中已释放内存块.
size_diff-
旧快照和新快照之间的内存块总大小(以字节为单位)的差异(
int):0如果内存块已在新快照中分配了
traceback-
Traceback其中分配了内存块,
Tracebackinstance.
Trace
- class
tracemalloc.Trace -
追踪内存块
Snapshot.traces属性是一系列的Traceinstances.更改版本3.6:添加了
domain属性domain-
内存块的地址空间(
int)。只读属性.tracemalloc使用域
0来跟踪由Python做出的内存分配。C扩展可以使用其他域来跟踪其他资源.
size-
内存块的大小以字节为单位(
int).
traceback-
在内存块分配的地方,
Tracebackinstance.
Traceback
- class
tracemalloc.Traceback -
Frame实例排序从最旧的帧到最近的帧.追溯至少包含
1框架如果tracemalloc模块失败得到一个框架,文件名"<unknown>"在行号0被使用.拍摄快照时,痕迹追溯仅限于
get_traceback_limit()帧。见take_snapshot()功能。Trace.tracebackattribute是Tracebackinstance.更改版本3.7:帧现在从最旧到最近排序,而不是从最近到最旧.
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())