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.get_object_traceback(obj)¶
Python オブジェクト obj が割り当てられた場所のトレースバックを取得します。
Tracebackインスタンスを返します。tracemallocモジュールがメモリ割り当てをトレースしていない場合や、そのオブジェクトの割り当てをトレースしなかった場合はNoneを返します。gc.get_referrers()やsys.getsizeof()関数も参照してください。
- tracemalloc.get_traceback_limit()¶
トレースのトレースバック内に格納されている最大フレーム数を取得します。
The
tracemallocmodule 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です。
- 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) と-Xtracemalloc=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¶
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です。
- filter_traces(filters)¶
フィルタリングした
tracesシーケンスを持つ新しいSnapshotインスタンスを作成します。 filters はDomainFilterおよびFilterインスタンスのリストです。 filters が空のリストの場合は、トレースをコピーした新しいSnapshotインスタンスを返します。包含フィルタはすべてまとめて適用され、どの包含フィルタにもマッチしないトレースは無視されます。 また、少なくとも 1 つの除外フィルタにマッチするトレースは無視されます。
バージョン 3.6 で変更: filters に
DomainFilterインスタンスも指定できるようになりました。
- 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)。
StatisticDiff¶
- class tracemalloc.StatisticDiff¶
新旧
Snapshotインスタンスのメモリ割り当ての統計差です。Snapshot.compare_to()はStatisticDiffインスタンスのリストを返します。Statisticクラスも参照してください。- count¶
新しいスナップショット内のメモリブロックの数 (
int) です。新しいスナップショット内でメモリブロックが解放された場合は0です。
- count_diff¶
新旧スナップショットのメモリブロック数の差 (
int) です。メモリブロックが新しいスナップショット内で割り当てられた場合は0です。
- size¶
新しいスナップショット内のメモリブロックのバイト単位での総サイズ (
int) です。新しいスナップショット内でメモリブロックが解放された場合は0です。
- size_diff¶
新旧スナップショットのバイト単位での総サイズの差 (
int) です。メモリブロックが新しいスナップショット内で割り当てられた場合は0です。
Trace¶
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())