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

Added in version 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()

# ... アプリケーションを実行する ...

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()
# ... アプリケーションを開始する ...

snapshot1 = tracemalloc.take_snapshot()
# ... メモリリークが起きる関数を呼び出す ...
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)

モジュールのデータ (バイトコードと定数) を読み込むために importlib モジュールが割り当てたメモリが最も多く、870.1 KiB であることが分かります。 トレースバックは importlib が直近にデータを読み込んだ場所、つまり doctest モジュールの import pdb 行を示しています。 新しいモジュールを読み込むと、トレースバックが変わることがあります。

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

# ... アプリケーションを実行する ...

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

トレース中の全メモリブロックの現在のサイズとピーク時のサイズを記録する

次のコードは、0 + 1 + 2 + ... のような和を 2 回計算します。 数値のリストを作成する非効率な方法で、このリストは一時的に多くのメモリを消費します。 get_traced_memory() と reset_peak() を使うと、和を計算した後の小さなメモリ使用量と、計算中のピーク時のメモリ使用量の両方を確認できます。

import tracemalloc

tracemalloc.start()

# コード例: 大きな一時リストを使って和を計算する
large_sum = sum(list(range(100000)))

first_size, first_peak = tracemalloc.get_traced_memory()

tracemalloc.reset_peak()

# コード例: 小さな一時リストを使って和を計算する
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

reset_peak() を使ったことで、small_sum の計算中のピークを正確に記録できました。 このピークは start() の呼び出し以降のメモリブロック全体のピークサイズよりもはるかに小さい値です。 reset_peak() を呼び出さなければ、second_peak は large_sum の計算中のピークのままです (つまり first_peak と等しくなります)。 この例では、どちらのピークも最終的なメモリ使用量を大幅に上回っています。 これは、不要な list の呼び出しをなくして 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()

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

The tracemalloc module must be tracing memory allocations to get the limit, otherwise an exception is raised.

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

tracemalloc.get_traced_memory()

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

tracemalloc.reset_peak()

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

tracemalloc モジュールがメモリ割り当てをトレースしていない場合は何もしません。

この関数は、記録されたピークサイズだけを変更します。 clear_traces() とは異なり、トレースを変更したり消去したりしません。 reset_peak() を呼び出す前に take_snapshot() で取得したスナップショットは、呼び出し後に取得したスナップショットと有意義に比較できます。

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

Added in version 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 です。 nframe は 1 以上でなければなりません。

Traceback.total_nframe 属性を調べると、元のトレースバックを構成していたフレームの総数も取得できます。

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)

メモリブロックのトレースを、アドレス空間 (ドメイン) でフィルタリングします。

Added in version 3.6.

inclusive

inclusive が True (含める) の場合は、アドレス空間 domain に割り当てられたメモリブロックにマッチします。

inclusive が False (除外する) の場合は、アドレス空間 domain 以外に割り当てられたメモリブロックにマッチします。

domain

メモリブロックのアドレス空間 (int)。 読み出し専用のプロパティです。

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

メモリブロックのアドレス空間 (int または None)。

tracemalloc はドメイン 0 を使って Python のメモリ割り当てをトレースします。 C 拡張は他のドメインを使って、他のリソースをトレースできます。

inclusive

inclusive が True (含める) の場合は、ファイル名が filename_pattern にマッチするファイルの、行番号 lineno で割り当てられたメモリブロックだけにマッチします。

inclusive が False (除外する) の場合は、ファイル名が filename_pattern にマッチするファイルの、行番号 lineno で割り当てられたメモリブロックを無視します。

lineno

フィルタの行番号です (int)。lineno が None の場合フィルタはあらゆる行番号にマッチします。

filename_pattern

フィルタのファイル名のパターンです (str)。読み出し専用のプロパティです。

all_frames

all_frames が True の場合トレースバックの全てのフレームをチェックします。all_frames が False の場合最新のフレームをチェックします。

トレースバックの上限が 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.size、 StatisticDiff.count_diff の絶対置、 Statistic.count、そして StatisticDiff.traceback です。

dump(filename)

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

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

filter_traces(filters)

フィルタリングした traces シーケンスを持つ新しい Snapshot インスタンスを作成します。 filters は DomainFilter および Filter インスタンスのリストです。 filters が空のリストの場合は、トレースをコピーした新しい Snapshot インスタンスを返します。

包含フィルタはすべてまとめて適用され、どの包含フィルタにもマッチしないトレースは無視されます。 また、少なくとも 1 つの除外フィルタにマッチするトレースは無視されます。

バージョン 3.6 で変更: filters に DomainFilter インスタンスも指定できるようになりました。

classmethod load(filename)

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

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

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

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

key_type

description

'filename'

ファイル名

'lineno'

ファイル名と行番号

'traceback'

traceback

cumulative が True の場合は、最新のフレームだけでなく、トレースのトレースバック内のすべてのフレームにメモリブロックのサイズと数を累積します。 累積モードを使えるのは、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

Difference of number of memory blocks between the old and the new snapshots (int).

size

新しいスナップショット内のメモリブロックのバイト単位での総サイズ (int) です。新しいスナップショット内でメモリブロックが解放された場合は 0 です。

size_diff

Difference of total size of memory blocks in bytes between the old and the new snapshots (int).

traceback

メモリブロックが割り当てられたトレースバックで、 Traceback のインスタンスです。

Trace

class tracemalloc.Trace

メモリブロックのトレース。

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

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

domain

メモリブロックのアドレス空間 (int)。 読み出し専用のプロパティです。

tracemalloc はドメイン 0 を使って Python のメモリ割り当てをトレースします。 C 拡張は他のドメインを使って、他のリソースをトレースできます。

size

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

traceback

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

Traceback

class tracemalloc.Traceback

最も古いフレームから最新のフレームの順に並んだ、Frame インスタンスのシーケンス。

トレースバックには少なくとも 1 フレームが含まれます。 tracemalloc モジュールがフレームを取得できなかった場合は、ファイル名 "<unknown>"、行番号 0 を使います。

スナップショットを取得するとき、トレースのトレースバックは get_traceback_limit() が返す数のフレームに制限されます。 take_snapshot() を参照してください。 元のトレースバックのフレーム数は Traceback.total_nframe 属性に保存されます。 これにより、トレースバックがフレーム数の上限によって切り詰められたかどうかが分かります。

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

バージョン 3.7 で変更: フレームの順序が、最新から最古ではなく、最古から最新になりました。

total_nframe

切り詰められる前のトレースバックを構成していたフレームの総数。 情報が利用できない場合は None になることがあります。

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

format(limit=None, most_recent_first=False)

トレースバックを書式化し、行のリストとして返します。 ソースコード行の取得には linecache モジュールを使います。 limit が指定されていて正の場合は、最新の limit フレームを書式化します。 limit が 0 以下の場合は、最も古い abs(limit) フレームを書式化します。 most_recent_first が True の場合は、書式化したフレームの順序を逆にし、最新のフレームを最後ではなく最初に返します。

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