# 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帧。默认情况下，内存块的跟踪仅存储最近的帧：限制为`1`。 nframe必须大于或等于`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`）。如果lineno为`None`，则过滤器将匹配任何行号。

  `filename_pattern`

    过滤器的文件名模式（`str`）。

  `all_frames`

    如果all_frames为`True`，则检查回溯的所有帧。如果all_frames为`False`，则只检查最近的帧。

    如果回溯限制为`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_diff`，`StatisticDiff.size`的绝对值，`StatisticDiff.count_diff` ，`Statistic.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'` | 追溯

    如果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.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())