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_tracebackobj

获取分配了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内存分配,则返回

Truetracemalloc,否则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)和-X tracemalloc=NFRAME命令行选项可用于在启动时开始跟踪.

参见stop(), is_tracing()get_traceback_limit()functions.

tracemalloc.stop ( )

停止跟踪Python内存分配:在Python内存分配器上卸载挂钩。同时清除所有以前收集的由Python分配的内存块的痕迹.

Call take_snapshot()在清理之前拍摄痕迹的快照.

参见start(), is_tracing()clear_traces()functions.

tracemalloc.take_snapshot (

拍摄Python分配的内存块的快照。返回一个新的Snapshotinstance.

快照不包括在tracemalloc模块开始跟踪内存分配之前分配的内存块.

跟踪的回溯仅限于get_traceback_limit()帧。使用 nframe的参数start()存储更多帧的功能.

tracemalloc模块必须跟踪内存分配以获取快照,请参阅start()功能。

另见get_object_traceback()功能。

DomainFilter

class tracemalloc.DomainFilterinclusive: bool, domain: int

通过地址空间(域)过滤内存块的痕迹.

版本3.6.

inclusive

如果inclusiveTrue(include),匹配地址空间中分配的内存块domain.

如果inclusiveFalse(不包括),匹配地址空间中未分配的内存块domain.

domain

地址记忆块的空间(int)。只读属性.

过滤

class tracemalloc.Filterinclusive: 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

内存块的地址空间(intNone).

tracemalloc使用域0跟踪内存分配由Python制作。C扩展可以使用其他域来跟踪其他资源.

inclusive

如果inclusiveTrue(包含),只匹配在名称匹配的文件中分配的内存块filename_pattern在行号lineno.

如果inclusiveFalse(不包括),忽略在行号//中匹配filename_pattern的文件中分配的内存块lineno.

lineno

过滤器的行号(int)如果linenoNone,则过滤器匹配任何行号.

filename_pattern

过滤器的文件名模式(str)。只读属性

all_frames

如果all_framesTrue,则检查所有回溯帧。如果all_framesFalse,则仅检查最近的帧.

如果回溯限制为1,则此属性无效。见get_traceback_limit()功能和Snapshot.traceback_limit属性

class tracemalloc.Frame

追溯框架

Tracebackclass是Frameinstances.

filename

文件名 (str).

lineno

电话号码 (int).

快照

class tracemalloc.Snapshot

Python分配的内存块跟踪快照.

take_snapshot()function创建快照实例.

compare_toold_snapshot: Snapshot, key_type: str, cumulative: bool=False

用旧快照计算差异。获取统计数据作为StatisticDiff实例的分类列表,按key_type.

分组查看Snapshot.statistics()方法key_typecumulativeparameters.

结果从最大到最小排序为:StatisticDiff.size_diff, StatisticDiff.size的绝对值,StatisticDiff.count_diff, Statistic.count的绝对值,然后是StatisticDiff.traceback.

dumpfilename

将快照写入文件

使用load()重新加载快照.

filter_traces (filters)

用过滤的Snapshot序列创建一个新的traces实例,filtersDomainFilterFilter实例的列表。如果filters是一个空列表,则返回一个新的Snapshot带有痕迹副本的实例.

一次性应用全包过滤器,如果无包含过滤器匹配,则忽略跟踪。如果至少有一个独占过滤器匹配,则忽略跟踪.

版本3.6更改:DomainFilter实例现在也被filters.

classmethod loadfilename

从文件加载快照

另见dump().

statisticskey_type: str, cumulative: bool=False

获取统计数据作为Statistic实例分组key_type

key_type的 描述
"filename" 文件名
"lineno" 文件名和行号
"traceback" 追溯

如果cumulativeTrue,累积大小和跟踪回溯的所有帧的内存块数,不仅是最近的帧。累积模式只能用于key_type等于"filename""lineno".

结果从最大到最小排序:Statistic.size, Statistic.count然后按Statistic.traceback.

traceback_limit

回溯中存储的最大帧数tracesget_traceback_limit()快照拍摄时

traces

Python分配的所有内存块的跟踪:Trace实例的序列

序列具有未定义的顺序。使用Snapshot.statistics()方法获取统计的排序列表.

Statistic

class tracemalloc.Statistic

关于内存分配的统计.

Snapshot.statistics()返回Statistic实例的列表

也见StatisticDiff class.

count

内存块的数量(int).

size

内存块的总大小以字节为单位(int).

traceback

分配内存块的回溯,Tracebackinstance.

StatisticDiff

class tracemalloc.StatisticDiff

新老内存分配的统计差异Snapshotinstance.

Snapshot.compare_to()返回StatisticDiff实例。另见Statistic class.

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:帧现在从最旧到最近排序,而不是从最近到最旧.

formatlimit=None, most_recent_first=False

将回溯格式化为带换行符的行列表。使用linecache从源代码中检索行的模块。如果设置了limit,如果limit为正,则格式化limit最近的帧。否则,格式化abs(limit)最旧的帧。如果most_recent_firstTrue,格式化帧的顺序颠倒过来,首先返回最近的帧而不是最后一帧.

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())