27.7. 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 コマンドラインオプションを使用してください。

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

We can see that Python loaded 4855 KiB data (bytecode and constants) from modules and that the collections module allocated 244 KiB to build namedtuple types.

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

27.7.1.2. 差を計算する

スナップショットを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

We can see that Python has loaded 8173 KiB of module data (bytecode and constants), and that this is 4428 KiB more than had been loaded before the tests, when the previous snapshot was taken. 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. メモリブロックのトレースバックを取得する

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

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.

27.7.1.4. 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]
        # 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 インスタンスか、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()

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

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

tracemalloc.start(nframe: int=1)

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

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

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' に置換されます。

例:

  • Filter(True, subprocess.__file__)subprocess モジュールのみを含みます
  • Filter(False, tracemalloc.__file__)tracemalloc モジュールのトレースを除外します
  • Filter(False, "<unknown>") は空のトレースバックを除外します

バージョン 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

Filename pattern of the filter (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, key_type: str, cumulative: bool=False)

Compute the differences with an old snapshot. Get statistics as a sorted list of StatisticDiff instances grouped by key_type.

See the Snapshot.statistics() method for key_type and cumulative parameters.

結果は降順でソートされます: キーは 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 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.

classmethod load(filename)

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

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

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

Get statistics as a sorted list of Statistic instances grouped by key_type:

key_type description
'filename' ファイル名
'lineno' ファイル名と行番号
'traceback' traceback

If cumulative is True, cumulate size and count of memory blocks of all frames of the traceback of a trace, not only the most recent frame. The cumulative mode can only be used with key_type equals to 'filename' and '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

Sequence of Frame instances sorted from the most recent frame to the oldest 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 Trace.traceback attribute is an instance of Traceback instance.

format(limit=None)

Format the traceback as a list of lines with newlines. Use the linecache module to retrieve lines from the source code. If limit is set, only format the limit most recent frames.

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