27.7. tracemalloc - 跟踪内存分配

版本3.4中的新功能。

源代码: Lib / tracemalloc.py

tracemalloc模块是一个跟踪由Python分配的内存块的调试工具。它提供以下信息:

  • 跟踪分配对象的位置
  • 每个文件名和每行编号分配的内存块的统计信息:已分配内存块的总大小,数量和平均大小
  • 计算两个快照之间的差异以检测内存泄漏

To trace most memory blocks allocated by Python, the module should be started as early as possible by setting the PYTHONTRACEMALLOC environment variable to 1, or by using -X tracemalloc command line option. 可以在运行时调用tracemalloc.start()函数以开始跟踪Python内存分配。

默认情况下,分配的内存块的跟踪只存储最近的帧(1帧)。要在启动时存储25帧:将 PYTHONTRACEMALLOC环境变量设置为25,或使用-X tracemalloc=25命令行选项。

27.7.1. Examples

27.7.1.1. Display the top 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. Compute differences

拍摄两张快照并显示差异:

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超过在测试之前加载的时间。Similarly, the linecache module has cached 940 KiB of Python source code to format tracebacks, all of it since the previous snapshot.

如果系统具有很少的可用内存,可以使用Snapshot.dump()方法将快照写入磁盘,以便离线分析快照。然后使用Snapshot.load()方法重新载入快照。

27.7.1.3. Get the traceback of a memory block

显示最大内存块回溯的代码:

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 The traceback is where the importlib loaded data most recently: on the import pdb line of the doctest module. 如果加载了新模块,回溯可能会更改。

27.7.1.4. Pretty top

忽略&lt; frozen importlib._bootstrap&gt;<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. Functions

tracemalloc.clear_traces()

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

另请参见stop()

tracemalloc.get_object_traceback(obj)

获取分配了Python对象obj的traceback。如果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

另请参见start()stop()函数。

tracemalloc.start(nframe: int=1)

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

Storing more than 1 frame is only useful to compute statistics grouped by 'traceback' or to compute cumulative statistics: see the Snapshot.compare_to() and Snapshot.statistics() methods.

存储更多帧会增加tracemalloc模块的内存和CPU开销。使用get_tracemalloc_memory()函数来测量tracemalloc模块使用的内存量。

The PYTHONTRACEMALLOC environment variable (PYTHONTRACEMALLOC=NFRAME) and the -X tracemalloc=NFRAME command line option can be used to start tracing at startup.

另请参见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. Filter

class tracemalloc.Filter(inclusive: bool, filename_pattern: str, lineno: int=None, all_frames: bool=False)

在内存块的跟踪上进行过滤。

有关filename_pattern的语法,请参见fnmatch.fnmatch()函数。'.pyc'文件扩展名替换为'.py'

例子:

  • 过滤器(True, 子过程.__文件__)仅包含subprocess
  • 筛选(False, tracemalloc .__ file __)排除tracemalloc
  • 过滤(False, “&lt;未知&gt;”)

在3.5版本中更改: '.pyo'文件扩展名不再替换为'.py'

inclusive

If inclusive is True (include), only trace memory blocks allocated in a file with a name matching filename_pattern at line number lineno.

If inclusive is False (exclude), ignore memory blocks allocated in a file with a name matching filename_pattern at line number lineno.

lineno

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

filename_pattern

过滤器的文件名模式(str)。

all_frames

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

如果回溯限制为1,则此属性不起作用。请参阅get_traceback_limit()函数和Snapshot.traceback_limit属性。

27.7.2.3. Frame

class tracemalloc.Frame

回溯的帧。

Traceback类是Frame实例的序列。

filename

文件名(str)。

lineno

行号(int)。

27.7.2.4. Snapshot

class tracemalloc.Snapshot

由Python分配的内存块的跟踪的快照。

take_snapshot()函数创建快照实例。

compare_to(old_snapshot: Snapshot, group_by: str, cumulative: bool=False)

计算与旧快照的差异。将统计信息作为按group_by分组的StatisticDiff实例的排序列表。

请参阅group_by累积参数的Snapshot.statistics()方法。

结果通过以下方法从最大到最小排序:StatisticDiff.size_diffStatisticDiff.size的绝对值,StatisticDiff.count_diffStatistic.count,然后按StatisticDiff.traceback

dump(filename)

将快照写入文件。

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

filter_traces(filters)

使用过滤的traces序列创建新的Snapshot实例,过滤器Filter实例的列表。如果过滤器是空列表,则返回一个带有跟踪副本的新的Snapshot实例。

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

classmethod load(filename)

从文件加载快照。

另请参见dump()

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

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

通过...分组描述
'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.5. Statistic

class tracemalloc.Statistic

内存分配统计。

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

另请参见StatisticDiff类。

count

内存块数​​(int)。

size

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

traceback

分配内存块的回溯,Traceback实例。

27.7.2.6. StatisticDiff

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.7. Trace

class tracemalloc.Trace

内存块的跟踪。

Snapshot.traces属性是Trace实例的序列。

size

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

traceback

分配内存块的回溯,Traceback实例。

27.7.2.8. Traceback

class tracemalloc.Traceback

从最近帧到最旧帧排序的Frame的序列。

回溯至少包含1帧。如果tracemalloc模块未能获取帧,则使用行号0的文件名"<unknown>"

捕获快照时,跟踪的回溯限制为get_traceback_limit()帧。请参阅take_snapshot()函数。

Trace.traceback属性是Traceback实例的实例。

format(limit=None)

将回溯格式设置为带换行符的行的列表。使用linecache模块从源代码中检索行。如果设置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())