Skip to main content

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

清除Python分配的内存块的痕迹。

参见 stop()

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

参见 start()stop() 功能。

tracemalloc.start(nframe: int=1)

开始跟踪Python内存分配:在Python内存分配器上安装钩子。收集的跟踪的跟踪将限于 nframe 帧。默认情况下,内存块的跟踪仅存储最近的帧:限制为 1nframe 必须大于或等于 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

class tracemalloc.DomainFilter(inclusive: bool, domain: int)

通过其地址空间(域)过滤内存块的跟踪。

3.6 新版功能.

inclusive

如果 inclusiveTrue (包括),则匹配在地址空间 domain 中分配的存储器块。

如果 inclusiveFalse (排除),则匹配未在地址空间 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 属性。

domain

存储器块(intNone)的地址空间。

inclusive

如果 inclusiveTrue (include),则只匹配在文件中分配的内存块的名称与 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 属性。

27.7.2.4. 帧

class tracemalloc.Frame

回溯的帧。

Traceback 类是 Frame 实例的序列。

filename

文件名(str)。

lineno

行号(int)。

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_bycumulative 参数,请参阅 Snapshot.statistics() 方法。

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

dump(filename)

将快照写入文件。

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

filter_traces(filters)

创建具有过滤的 traces 序列的新 Snapshot 实例,filtersDomainFilterFilter 实例的列表。如果 filters 是空列表,则返回带有跟踪副本的新 Snapshot 实例。

同时应用所有包含的过滤器,如果没有包含的过滤器匹配,则忽略跟踪。如果至少有一个排他过滤器匹配,则会忽略跟踪。

在 3.6 版更改: DomainFilter 实例现在也被 filters 接受。

classmethod load(filename)

从文件加载快照。

参见 dump()

statistics(group_by: str, cumulative: bool=False)

将统计信息作为按 group_by 分组的 Statistic 实例的排序列表:

通过...分组

描述

'filename'

filename

'lineno'

文件名和行号

'traceback'

追溯

如果 cumulativeTrue,则累加跟踪的回溯的所有帧的存储器块的大小和计数,而不仅是最近的帧。累积模式只能用于 group_by 等于 'filename''lineno'

结果从最大到最小排序:Statistic.sizeStatistic.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())