tracemalloc --- メモリ割り当ての追跡

バージョン 3.4 で追加.

ソースコード: Lib/tracemalloc.py


tracemallocモジュールは、Python が割り当てたメモリブロックをトレースするためのデバッグツールです。このモジュールは以下の情報を提供します。

  • オブジェクトが割り当てられた場所のトレースバック

  • ファイル名ごと、及び行ごとに割り当てられたメモリブロックの以下の統計を取ります:総サイズ、ブロック数、割り当てられたブロックの平均サイズ

  • メモリリークを検出するために2つのスナップショットの差を計算します。

Python が割り当てたメモリブロックの大半をトレースするには、 PYTHONTRACEMALLOC 環境変数を 1 に設定して可能な限り早くモジュールを開始させるか、-X tracemalloc コマンドラインオプションを使用してください。実行時に tracemalloc.start() を呼んで Python のメモリ割り当てのトレースを開始することが出来ます。

デフォルトでは、割り当てられたメモリブロック1つのトレースは最新1フレームを保存します。開始時に25フレームを保存するには、PYTHONTRACEMALLOC 環境変数を 25 に設定するか、-X tracemalloc=25 コマンドラインオプションを使用してください。

使用例

上位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 がモジュールから 4855 KiB のデータ (バイトコードで定数) を読み込んでいることと、collections モジュールが namedtuple 型をビルドするのに 244 KiB を割り当てていることが分かります。

オプションの詳細については Snapshot.statistics() を参照してください。

差を計算する

スナップショットを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 がモジュールデータ 8173 KiB (バイトコードと定数) を読み込み、 前回スナップショットを取ったとき、すなわちテストの前に読み込んだ量より 4428 KiB 多いということが分かります。 同様に、 linecache モジュールはトレースバックの書式化に Python ソースコード 940 KiB をキャッシュし、その全ては前回のスナップショットの後に行われたことが分かります。

システムに空きメモリがほとんどない場合、スナップショットをオフラインで解析するための Snapshot.dump() メソッドを使用して、スナップショットをディスクに書き込むことが出来ます。 そして Snapshot.load() メソッドを使用してスナップショットを再読み込みします。

メモリブロックのトレースバックを取得する

最大のメモリブロックのトレースバックを表示するコードです:

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)

We can see that the most memory was allocated in the importlib module to load data (bytecode and constants) from modules: 870.1 KiB. The traceback is where the importlib loaded data most recently: on the import pdb line of the doctest module. The traceback may change if a new module is loaded.

top を整形化する

<frozen importlib._bootstrap> および <unknown> ファイルを無視して、 メモリ割り当て量の上位10を整形化して表示するコードです:

import linecache
import os
import tracemalloc

def 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]
        print("#%s: %s:%s: %.1f KiB"
              % (index, frame.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() を参照してください。

Record the current and peak size of all traced memory blocks

The following code computes two sums like 0 + 1 + 2 + ... inefficiently, by creating a list of those numbers. This list consumes a lot of memory temporarily. We can use get_traced_memory() and reset_peak() to observe the small memory usage after the sum is computed as well as the peak memory usage during the computations:

import tracemalloc

tracemalloc.start()

# Example code: compute a sum with a large temporary list
large_sum = sum(list(range(100000)))

first_size, first_peak = tracemalloc.get_traced_memory()

tracemalloc.reset_peak()

# Example code: compute a sum with a small temporary list
small_sum = sum(list(range(1000)))

second_size, second_peak = tracemalloc.get_traced_memory()

print(f"{first_size=}, {first_peak=}")
print(f"{second_size=}, {second_peak=}")

出力:

first_size=664, first_peak=3592984
second_size=804, second_peak=29704

Using reset_peak() ensured we could accurately record the peak during the computation of small_sum, even though it is much smaller than the overall peak size of memory blocks since the start() call. Without the call to reset_peak(), second_peak would still be the peak from the computation large_sum (that is, equal to first_peak). In this case, both peaks are much higher than the final memory usage, and which suggests we could optimise (by removing the unnecessary call to list, and writing sum(range(...))).

API

関数

tracemalloc.clear_traces()

Python が割り当てたメモリブロックのトレースを消去します。

stop() を参照してください。

tracemalloc.get_object_traceback(obj)

Python オブジェクト obj が割り当てられたトレースバックを取得します。Traceback インスタンスか、tracemalloc モジュールがメモリ割り当てをトレースしていない場合かオブジェクトの割り当てをトレースしていない場合は、None を返します。

gc.get_referrers()sys.getsizeof() 関数も参照してください。

tracemalloc.get_traceback_limit()

トレースのトレースバック内に格納されている最大フレーム数を取得します。

tracemalloc モジュールは上限を取得するためにメモリ割り当てをトレースしていなければなりません。そうでなければ例外が送出されます。

start() 関数で上限を設定します。

tracemalloc.get_traced_memory()

tracemalloc モジュールがトレースするメモリブロックの現在のサイズと最大時のサイズをタプルとして取得します: (current: int, peak: int)

tracemalloc.reset_peak()

tracemalloc モジュールがトレースするメモリブロックの最大時のサイズを現在のサイズに設定します。

Do nothing if the tracemalloc module is not tracing memory allocations.

This function only modifies the recorded peak size, and does not modify or clear any traces, unlike clear_traces(). Snapshots taken with take_snapshot() before a call to reset_peak() can be meaningfully compared to snapshots taken after the call.

get_traced_memory() も参照してください。

バージョン 3.9 で追加.

tracemalloc.get_tracemalloc_memory()

tracemalloc モジュールがメモリブロックのトレースを保存するのに使用しているメモリ使用量をバイト単位で取得します。int を返します。

tracemalloc.is_tracing()

tracemalloc モジュールが Python のメモリ割り当てをトレースしていれば True を、そうでなければ False を返します。

start() ならびに stop() 関数も参照してください。

tracemalloc.start(nframe: int=1)

Python のメモリ割り当てのトレースを開始します: Python メモリアロケータにフックします。 トレースの収集されたトレースバックは nframe フレームに制限されます。 デフォルトでは、あるブロックのトレースは最新のフレームのみを保存します、つまり上限は 1 です。 nframe1 以上でなければなりません。

You can still read the original number of total frames that composed the traceback by looking at the Traceback.total_nframe attribute.

1 より多くのフレームを保存するのは 'traceback' でグループ化された統計や累積的な統計を計算する場合にのみ有用です。 Snapshot.compare_to() および Snapshot.statistics() メソッドを参照してください。

保存するフレーム数を増やすと tracemalloc モジュールのメモリと CPU のオーバーヘッドは増加します。 tracemalloc モジュールが使用しているメモリ量を調べるには get_tracemalloc_memory() 関数を使用してください。

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() 関数を参照してください。

DomainFilter

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

Filter traces of memory blocks by their address space (domain).

バージョン 3.6 で追加.

inclusive

If inclusive is True (include), match memory blocks allocated in the address space domain.

If inclusive is False (exclude), match memory blocks not allocated in the address space domain.

domain

Address space of a memory block (int). Read-only property.

Filter

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

Address space of a memory block (int or None).

tracemalloc uses the domain 0 to trace memory allocations made by Python. C extensions can use other domains to trace other resources.

inclusive

If inclusive is True (include), only match 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 属性を参照してください。

Frame

class tracemalloc.Frame

トレースバックのフレームです。

Traceback クラスは Frame インスタンスのシークエンスです。

filename

ファイル名 (str)。

lineno

行番号 (int)。

Snapshot

class tracemalloc.Snapshot

Python が割り当てたメモリブロックのトレースのスナップショットです。

take_snapshot() 関数はスナップショットのインスタンスを作ります。

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

古いスナップショットとの差を計算します。 key_type でグループ化された StatisticDiff インスタンスのソート済みリストとして統計を取得します。

key_type および cumulative 引数については Snapshot.statistics() メソッドを参照してください。

結果は降順でソートされます: キーは StatisticDiff.size_diff の絶対値、 StatisticDiff.sizeStatisticDiff.count_diff の絶対置、 Statistic.count、そして StatisticDiff.traceback です。

dump(filename)

スナップショットをファイルに書き込みます。

スナップショットをリロードするには load() を使用します。

filter_traces(filters)

Create a new Snapshot instance with a filtered traces sequence, filters is a list of DomainFilter and Filter instances. If filters is an empty list, return a new Snapshot instance with a copy of the traces.

All inclusive filters are applied at once, a trace is ignored if no inclusive filters match it. A trace is ignored if at least one exclusive filter matches it.

バージョン 3.6 で変更: DomainFilter instances are now also accepted in filters.

classmethod load(filename)

スナップショットをファイルからロードします。

dump() を参照してください。

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

key_type でグループ化された Statistic インスタンスのソート済みリストとして統計を取得します:

key_type

description

'filename'

ファイル名

'lineno'

ファイル名と行番号

'traceback'

traceback

cumulativeTrue の場合、最新のフレームだけでなく、トレースのトレースバックの全フレームのメモリーブロックについて大きさと数を累積します。 累積モードは 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 インスタンスの一覧を返します。

StatisticDiff クラスも参照してください。

count

メモリブロック数 (int)。

size

メモリブロックのバイト単位の総サイズ (int)。

traceback

メモリブロックが割り当てられているトレースバック。Traceback インスタンス。

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 のインスタンスです。

Trace

class tracemalloc.Trace

メモリブロックをトレースします。

Snapshot.traces 属性は Trace インスタンスのシークエンスです。

バージョン 3.6 で変更: domain 属性が追加されました。

domain

Address space of a memory block (int). Read-only property.

tracemalloc uses the domain 0 to trace memory allocations made by Python. C extensions can use other domains to trace other resources.

size

メモリブロックのバイト単位のサイズ (int)。

traceback

メモリブロックが割り当てられているトレースバック。Traceback インスタンス。

Traceback

class tracemalloc.Traceback

Sequence of Frame instances sorted from the oldest frame to the most recent frame.

A traceback contains at least 1 frame. If the tracemalloc module failed to get a frame, the filename "<unknown>" at line number 0 is used.

When a snapshot is taken, tracebacks of traces are limited to get_traceback_limit() frames. See the take_snapshot() function. The original number of frames of the traceback is stored in the Traceback.total_nframe attribute. That allows to know if a traceback has been truncated by the traceback limit.

Trace.traceback 属性は Traceback のインスタンスです。

バージョン 3.7 で変更: Frames are now sorted from the oldest to the most recent, instead of most recent to oldest.

total_nframe

Total number of frames that composed the traceback before truncation. This attribute can be set to None if the information is not available.

バージョン 3.9 で変更: Traceback.total_nframe 属性が追加されました。

format(limit=None, most_recent_first=False)

Format the traceback as a list of lines. Use the linecache module to retrieve lines from the source code. If limit is set, format the limit most recent frames if limit is positive. Otherwise, format the abs(limit) oldest frames. If most_recent_first is True, the order of the formatted frames is reversed, returning the most recent frame first instead of last.

Similar to the traceback.format_tb() function, except that format() does not include newlines.

以下はプログラム例です:

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