profiling --- Python のプロファイラ

Added in version 3.15.

ソースコード: Lib/profiling/


プロファイリングの概要

プロファイル は、プログラムの各部分が何回、どれだけの時間実行されたかを示す統計情報です。 この情報は、性能上のボトルネックを特定し、最適化の方針を決めるのに役立ちます。 Python は、この情報を収集するために、根本的に異なる二つの方法を提供しています。 統計的サンプリングと決定論的トレースです。

profiling パッケージは、Python に組み込まれたプロファイリングツールを一つの名前空間にまとめます。 異なるプロファイリング手法を実装する、次の二つのサブモジュールを含みます。

profiling.sampling

コールスタックを定期的にサンプリングする統計的プロファイラです。 スクリプトを直接実行したり、PID を指定して実行中のプロセスに接続したりできます。 ほぼオーバーヘッドなしで、複数の出力形式 (フレームグラフ、ヒートマップ、Firefox Profiler)、GIL の分析、GC の追跡、複数のプロファイリングモード (実時間、CPU、GIL) を提供します。

profiling.tracing

A deterministic profiler that traces every function call, return, and exception event. Provides exact call counts and precise timing information, capturing every invocation including very fast functions.

注釈

プロファイラのモジュールは、プログラムの実行プロファイルを得るためのもので、ベンチマークを目的としていません。 ベンチマークには、十分に正確な時間計測ができる timeit モジュールを使ってください。 Python のコードと C のコードを比較する際は、この違いが特に重要です。 決定論的プロファイラは Python のコードにオーバーヘッドを加えますが、C レベルの関数には加えないため、比較結果が偏る可能性があります。

プロファイラの選択

ほとんどの性能分析では、統計的プロファイラ (profiling.sampling) を使ってください。 オーバーヘッドが非常に小さく、開発環境と本番環境の両方で使えます。 また、フレームグラフ、ヒートマップ、GIL 分析など、豊富な可視化機能を提供します。

正確な呼び出し回数 が必要で、関数呼び出しを一つも見逃せない場合は、決定論的プロファイラ (profiling.tracing) を使ってください。 すべての関数呼び出しと復帰を計測するため、サンプリング間隔の間に完了する非常に短い関数も記録できます。 その代わり、オーバーヘッドは大きくなります。

主な違いを次の表にまとめます。

機能

統計的サンプリング (profiling.sampling)

決定論的プロファイリング (profiling.tracing)

オーバーヘッド

ほぼなし

中程度

精度

統計的な推定

正確な呼び出し回数

出力形式

pstats、フレームグラフ、ヒートマップ、gecko、collapsed

pstats

プロファイリングモード

実時間、CPU、GIL

実時間

特殊なフレーム

GC、ネイティブ (C 拡張)

N/A

PID を指定した接続

可

不可

統計的サンプリングを使う場面

ほとんどの性能分析では、統計的プロファイラ (profiling.sampling) を推奨します。 profiling.tracing と同じように使えます。

python -m profiling.sampling run script.py

サンプリングプロファイラの大きな特長の一つは、出力形式が多彩なことです。 従来の pstats の表に加え、呼び出し階層を可視化する対話的なフレームグラフ、コードのどの行に時間がかかっているかを示すソースコードのヒートマップ、タイムラインに沿って分析するための Firefox Profiler 形式の出力を生成できます。

また、決定論的プロファイリングでは捉えられない Python インタープリタの動作も調べられます。 --mode gil でマルチスレッドのコードの GIL 競合を特定し、--mode cpu で I/O 待ちを除いた実際の CPU 時間を計測できます。 また、<GC> フレームを調べると、ガベージコレクションのオーバーヘッドを把握できます。 --native オプションは C 拡張内で費やした時間を示し、Python のオーバーヘッドとライブラリの性能を区別するのに役立ちます。

マルチスレッドのアプリケーションでは、-a オプションで全スレッドを同時にサンプリングし、処理の分担を確認できます。 本番環境のデバッグでは、attach コマンドで PID を指定し、再起動やコードの変更なしに実行中の Python プロセスへ接続できます。

決定論的トレースを使う場面

決定論的プロファイラ (profiling.tracing) は、すべての関数呼び出しと復帰を計測します。 サンプリングよりもオーバーヘッドは大きくなりますが、プログラムの実行を漏れなく記録できます。

決定論的トレースを選ぶ主な理由は、正確な呼び出し回数が必要な場合です。 統計的プロファイリングはサンプリングから頻度を推定するため、サンプル間で終了する短い関数の回数を少なく見積もることがあります。 最適化によって関数呼び出しが実際に減ったかを確認したい場合や、呼び出しグラフ全体を追跡して呼び出し元と呼び出し先の関係を理解したい場合は、決定論的トレースが適しています。

決定論的トレースは、マイクロ秒単位で実行される関数の記録にも優れています。 そのような関数は統計的なサンプルに十分な頻度で現れない可能性がありますが、決定論的トレースは実行時間に関係なくすべての呼び出しを記録します。

クイックスタート

この節では、プロファイリングを始めるために必要な最小限の手順を示します。 詳しい説明は、各プロファイラの専用ページを参照してください。

統計的プロファイリング

スクリプトをプロファイルするには、profiling.sampling モジュールの run コマンドを使います。

python -m profiling.sampling run script.py
python -m profiling.sampling run -m mypackage.module

これにより、プロファイラの下でスクリプトを実行し、どこで時間を使ったかの概要を表示します。 対話的なフレームグラフを生成するには、次のようにします。

python -m profiling.sampling run --flamegraph script.py

すでに実行中のプロセスをプロファイルするには、attach コマンドにプロセス ID を指定します。

python -m profiling.sampling attach 1234

設定を変更するには、サンプリング間隔 (マイクロ秒) と計測時間 (秒) を指定します。

python -m profiling.sampling run -i 50 -d 30 script.py

決定論的プロファイリング

コマンドラインからスクリプトをプロファイルするには、次のようにします。

python -m profiling.tracing script.py

プログラムからコードの一部をプロファイルするには、次のようにします。

import profiling.tracing
profiling.tracing.run('my_function()')

指定したコードをプロファイラの下で実行し、正確な関数呼び出し回数と時間の概要を表示します。

プロファイル出力の読み方

どちらのプロファイラも関数単位の統計を収集しますが、表示形式が異なります。 サンプリングプロファイラには複数の可視化形式 (フレームグラフ、ヒートマップ、Firefox Profiler、pstats の表) があり、決定論的プロファイラは pstats 互換の出力を生成します。 形式によらず、基本的な概念は同じです。

プロファイリングの主な概念は次のとおりです。

直接時間 (self time または tottime とも呼びます)

呼び出し先の関数で費やした時間を除き、関数自身のコードを実行した時間です。 直接時間が長ければ、その関数に時間のかかる処理が含まれていることを示します。

累積時間 (total time または cumtime とも呼びます)

その関数と、そこから呼び出したすべての関数で費やした時間です。 呼び出し先の部分木全体を含めた、関数呼び出しの総コストを表します。

呼び出し回数 (ncalls または samples とも呼びます)

関数を呼び出した回数 (決定論的プロファイリング)、またはサンプリングした回数 (統計的プロファイリング) です。 決定論的プロファイリングでは正確な回数です。 統計的プロファイリングでは、その関数がスタックのサンプルに現れた回数を表します。

プリミティブな呼び出し

再帰によって生じたものではない呼び出しです。 関数が再帰する場合、総呼び出し回数には再帰呼び出しも含みますが、プリミティブな呼び出しには最初の呼び出しだけを数えます。 総呼び出し回数/プリミティブな呼び出し回数 の形で表示します (例えば 3/1 は、総呼び出し回数が 3 回、プリミティブな呼び出しが 1 回であることを表します)。

呼び出し元と呼び出し先の関係

ある関数をどの関数が呼び出したか (呼び出し元)、その関数がどの関数を呼び出したか (呼び出し先) を示します。 フレームグラフでは入れ子の長方形で可視化し、pstats では print_callers() と print_callees() メソッドで表示できます。

従来のコードとの互換性

後方互換性のため、cProfile モジュールは profiling.tracing の別名として引き続き利用できます。 import cProfile を使う既存のコードは、今後のすべての Python バージョンで変更せずに動作します。

バージョン 3.15 で非推奨: 純粋な Python による profile モジュールは非推奨で、Python 3.17 で削除されます。 代わりに profiling.tracing (またはその別名 cProfile) を使ってください。 移行については profile を参照してください。

参考

profiling.sampling

フレームグラフ、ヒートマップ、GIL 分析を備えた統計的サンプリングプロファイラです。 ほとんどのユーザに推奨します。

profiling.tracing

正確な呼び出し回数を取得するための決定論的トレースプロファイラです。

pstats

プロファイルデータの統計分析と整形。

timeit

短いコードの実行時間を測定するモジュールです。

サブモジュール