"profiling.sampling" --- 統計的プロファイラ
*******************************************

Added in version 3.15.

**ソースコード:** Lib/profiling/sampling/

======================================================================

[画像: Tachyon のロゴ][画像]

**Tachyon** という名前の "profiling.sampling" モジュールは、スタックを
定期的にサンプリングして Python プログラムを統計的にプロファイリングし
ます。 Tachyon はスクリプトを直接実行することも、コードの変更や再起動
なしに実行中の Python プロセスに接続することもできます。 サンプリング
は対象プロセスの外部で行うため、オーバーヘッドはほぼゼロです。 そのた
め、開発環境と本番環境の両方に適しています。


統計的プロファイリングとは
==========================

統計的プロファイリングは、コールスタックのスナップショットを定期的に取
得して、プログラムの動作を捉えます。 Tachyon は決定論的プロファイラの
ようにすべての関数呼び出しと復帰を計測するのではなく、一定の間隔でコー
ルスタックを読み取り、その時点で実行中のコードを記録します。

この方法は、多くの CPU 時間を使う関数ほど、収集したサンプルに頻繁に現
れるという単純な原理に基づいています。 Tachyon はプロファイリング中に
数千のサンプルを集め、どこで時間を費やしているかを統計的に正確に推定し
ます。 サンプルが多いほど、推定の精度は高くなります。

注釈:

  このドキュメントの HTML 版では、サンプリングによるプロファイリングの
  対話的な可視化を利用できます。


時間の推定方法
--------------

Tachyon の出力に表示される時間は、直接測定した値ではなく、**サンプル数
から求めた推定値** です。 Tachyon は収集したサンプルに各関数が現れる回
数を数え、サンプリング間隔を掛けて時間を推定します。

例えば、サンプリングレート 10 kHz で 10 秒間プロファイルすると、
Tachyon は約 100,000 個のサンプルを収集します。 ある関数が 5,000 個の
サンプル (全体の 5%) に現れた場合、Tachyon はその関数が 10 秒の 5%、つ
まり約 500 ミリ秒を使ったと推定します。 これは統計的な推定であり、正確
な測定値ではありません。

推定の精度はサンプル数に依存します。 100,000 個のサンプルで 5% と表示
される関数の誤差範囲は、およそ ±0.5% です。 サンプルが 1,000 個しかな
い場合、同じ 5% という値でも、実際の時間の割合は 3% から 7% のどこかか
もしれません。

プロファイリングの時間を長くし、サンプリング間隔を短くすると結果の信頼
性が増すのは、より多くのサンプルを集められるためです。 ほとんどの性能
分析では、デフォルトの設定でボトルネックを特定し、最適化の方針を決める
のに十分な精度が得られます。

サンプリングは統計的な手法なので、結果は実行ごとに多少変わります。 あ
る実行で 12% と表示された関数が、次には 11% や 13% になるかもしれませ
ん。 これは正常で、想定される動作です。 厳密な割合よりも全体的な傾向に
注目し、実行ごとの小さな変動を気にしすぎないでください。


別の方法を使う場面
------------------

統計的サンプリングが、すべての状況に最適とは限りません。

1 秒未満で完了する非常に短いスクリプトでは、信頼できる結果を得るのに十
分なサンプルを集められないことがあります。 代わりに
"profiling.tracing" を使うか、スクリプトをループで繰り返し実行してプロ
ファイリングの時間を延ばしてください。

正確な呼び出し回数が必要な場合、サンプリングでは取得できません。 サン
プリングはスナップショットから頻度を推定するため、関数が呼び出された正
確な回数を知るには "profiling.tracing" を使ってください。

差が 1〜2% しかない可能性のある二つの実装を比較すると、サンプリングの
ノイズによって本当の差が見えなくなることがあります。 マイクロベンチマ
ークには "timeit" を、精密な計測には "profiling.tracing" を使ってくだ
さい。

"profiling.tracing" との主な違いは計測方法です。 トレース型プロファイ
ラはコードの実行に計測処理を組み込み、すべての関数呼び出しと復帰を記録
します。 正確な呼び出し回数と時間が得られますが、関数呼び出しごとにオ
ーバーヘッドが生じます。 一方、サンプリングプロファイラは、プログラム
の実行を変更せず、一定の間隔で外部から観測します。 トレースは誰かが後
ろをついてきて一歩ずつ記録するようなもので、サンプリングは毎秒写真を撮
り、その写真から移動経路を推測するようなものです。

この外部から観測する仕組みにより、サンプリングによるプロファイリングは
本番環境でも実用的に使えます。 対象プログラムの内部で計測用のコードが
動かないため、プログラムは通常の速度で動作し、サンプリング中に停止や一
時停止もしません。 Tachyon はプロセスが実行を続けている間に、そのメモ
リからコールスタックを直接読み取ります。 アプリケーションに観測を意識
させることなく、稼働中のサーバに接続し、データを収集して切断できます。
その代わり、非常に短い関数がサンプル間で完了した場合は、見逃すことがあ
ります。

統計的プロファイリングは、「プログラムのどこに時間がかかっているか」を
調べるのに優れています。 決定論的プロファイリングのオーバーヘッドを許
容できない本番のコードでも、処理の集中箇所やボトルネックを明らかにでき
ます。 正確な呼び出し回数や完全な呼び出しグラフには、代わりに
"profiling.tracing" を使ってください。


簡単な使用例
============

スクリプトをプロファイルし、その場で結果を確認します。

   python -m profiling.sampling run script.py

引数付きでモジュールをプロファイルします。

   python -m profiling.sampling run -m mypackage.module arg1 arg2

対話的なフレームグラフを生成します。

   python -m profiling.sampling run --flamegraph -o profile.html script.py

PID を指定して実行中のプロセスに接続します。

   python -m profiling.sampling attach 12345

実行中のプロセスのスタックのスナップショットを一回表示します。

   python -m profiling.sampling dump 12345

ライブモードでリアルタイムに監視します (終了するには "q" を押します)。

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

サンプリングレートを上げて 60 秒間プロファイルします。

   python -m profiling.sampling run -d 60 -r 20khz script.py

行ごとのヒートマップを生成します。

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

オペコード単位のプロファイリングを有効にして、実行中のバイトコード命令
を確認します。

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


コマンド
========

Tachyon はいくつかのサブコマンドで操作します。 "run" と "attach" は一
定期間サンプルを収集し、"dump" は一回のスナップショットを取得し、
"replay" はバイナリ形式のプロファイルを別の形式に変換します。


"run" コマンド
--------------

"run" コマンドは Python スクリプトまたはモジュールを起動し、起動時から
プロファイルします。

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

スクリプトをプロファイルするときは、対象をサブプロセスとして起動し、初
期化を待ってからサンプルの収集を開始します。 "-m" フラグは、対象をモジ
ュールとして実行することを指定します ("python -m" と同等です)。 対象の
後に指定した引数は、プロファイルするプログラムにそのまま渡されます。

   python -m profiling.sampling run script.py --config settings.yaml


"attach" コマンド
-----------------

"attach" コマンドは、プロセス ID を指定して、すでに実行中の Python プ
ロセスに接続します。

   python -m profiling.sampling attach 12345

このコマンドは、本番システムの性能問題を調査する際に特に役立ちます。
対象プロセスの変更も再起動も不要です。 プロファイラは接続後に指定した
時間だけサンプルを収集し、その後切断して結果を出力します。

   python -m profiling.sampling attach --live 12345
   python -m profiling.sampling attach --flamegraph -d 30 -o profile.html 12345

ほとんどのシステムでは、別のプロセスに接続するには適切な権限が必要です
。 プラットフォームごとの要件については プラットフォームの要件 を参照
してください。


"dump" コマンド
---------------

"dump" コマンドは、トレースバックのような形式で、実行中のプロセスの
Python スタックのスナップショットを一回表示して終了します。

   python -m profiling.sampling dump 12345

"attach" と異なり、"dump" はサンプリングを繰り返さず、スタックを一度だ
け読み取ります。 ハングしたプロセスや応答しないプロセスの調査、あるい
は「このプロセスは今何をしているか」の確認に役立ちます。

出力はトレースバックと同じように、最後に直近の呼び出しを表示し、各スレ
ッドに現在の状態を付記します (メインスレッド、GIL 保持中、CPU 上で実行
中、GIL 待ち、例外あり、またはアイドル)。

   Stack dump for PID 12345, thread 140735 (main thread, has GIL, on CPU; most recent call last):
     File "server.py", line 28, in serve
       await handle_request(req)
     File "handler.py", line 91, in handle_request
       result = expensive_call(req)

対象のソースファイルを読み取れる場合、"dump" は各フレームのソース行を
表示し、実行中の式を強調表示します。

"attach" と同様に、"dump" には対象プロセスのメモリを読む権限が必要です
。 プラットフォームの要件 を参照してください。

"dump" コマンドは次のオプションに対応しています。

"-a", "--all-threads"
   対象プロセスのすべてのスレッドをダンプします。 このフラグを指定しな
   ければ、メインスレッドだけを表示します。

"--native"
   C 拡張やその他の Python 以外のコードへの遷移を示す、人工的な
   "<native>" フレームを含めます。

"--no-gc"
   ガベージコレクションの実行中であることを示す、人工的な "<GC>" フレ
   ームを非表示にします。

"--opcodes"
   各フレームに、スレッドが現在実行中のバイトコードのオペコードを付記
   します (例えば "opcode=CALL_KW")。 適応型インタープリタが選んだ特殊
   化の確認など、命令単位の調査に役立ちます。

"--async-aware"
   "await" の境界をまたいでスタックを再構成します。 "dump" はタスクの
   グラフをたどり、タスクごとに一つの節を出力します。 待機関係にあるコ
   ルーチンの間は "<task>" マーカーで区切ります。

"--async-mode {running,all}"
   "--async-aware" が有効なときに、どのタスクを含めるかを制御します。
   "running" は各スレッドで現在実行中のタスクだけを表示し、"all"
   ("dump" のデフォルト) は待機によって中断しているタスクも含めます。
   "attach" では、このフラグのデフォルトは "running" です。 一回のスナ
   ップショットではタスクのグラフ全体を表示する方が役立つため、"dump"
   のデフォルトは "all" になっています。

"--blocking"
   スタックを読み取る間は対象の全スレッドを一時停止し、その後再開しま
   す。 対象が短時間止まる代わりに、完全に整合したスナップショットを保
   証します。 指定しない場合、"dump" は対象が動作したままメモリを読み
   取ります。 その方が高速ですが、まれに不整合のあるスタックが得られる
   ことがあります。


"replay" コマンド
-----------------

"replay" コマンドは、バイナリ形式のプロファイルファイルを別の出力形式
に変換します。

   python -m profiling.sampling replay profile.bin
   python -m profiling.sampling replay --flamegraph -o profile.html profile.bin

このコマンドは、バイナリ形式で取得したプロファイリングデータを後から分
析したり、可視化用の形式に変換したりする際に役立ちます。 バイナリ形式
のプロファイルは、再度プロファイリングを行わずに、何度でも別の形式で再
生できます。

   # バイナリから pstats に変換します (デフォルトでは標準出力に表示)
   python -m profiling.sampling replay profile.bin

   # バイナリからフレームグラフに変換します
   python -m profiling.sampling replay --flamegraph -o output.html profile.bin

   # バイナリから Firefox Profiler 用の gecko 形式に変換します
   python -m profiling.sampling replay --gecko -o profile.json profile.bin

   # バイナリからヒートマップに変換します
   python -m profiling.sampling replay --heatmap -o my_heatmap profile.bin


本番環境でのプロファイリング
----------------------------

サンプリングプロファイラは、本番環境での使用を想定して設計されています
。 コードに計測処理を組み込む代わりに外部からメモリを読むため、対象プ
ロセスに測定できるほどのオーバーヘッドを与えません。 対象アプリケーシ
ョンは通常の速度で動作を続け、プロファイルされていることを意識せずに動
作します。

本番システムをプロファイルするときは、次の指針を参考にしてください。

最初は短時間 (10〜30 秒) で結果を確認し、統計的な精度がさらに必要なら
時間を延ばしてください。 デフォルトでは対象プロセスが終了するまでプロ
ファイリングを続けますが、通常はそれで主な処理の集中箇所を特定できます
。

可能なら、アクセスが最も集中する時間帯ではなく、通常を代表する負荷の下
でプロファイルしてください。 通常の動作中に収集したプロファイルは、異
常な負荷の急増時に収集したものより解釈しやすくなります。

The profiler itself consumes some CPU on the machine where it runs
(not on the target process). On the same machine, this is typically
negligible. When profiling remote processes, network latency does not
affect the target.

本番環境ではデータ量、同時実行の負荷、キャッシュの影響が異なるため、開
発環境とは違う結果になる場合があります。 これは想定されることで、多く
の場合、まさに捉えたい違いです。


プラットフォームの要件
----------------------

プロファイラは対象プロセスのメモリを読み取り、スタックトレースを取得し
ます。 ほとんどの OS では、そのために追加の権限が必要です。

**Linux**

Linux では、プロファイラは "ptrace" または "process_vm_readv" で対象プ
ロセスのメモリを読み取ります。 通常、次のいずれかが必要です。

* root として実行する

* "CAP_SYS_PTRACE" ケーパビリティを持つ

* Yama の ptrace スコープ "/proc/sys/kernel/yama/ptrace_scope" を調整
  する

デフォルトの ptrace_scope は 1 で、ptrace を実行できるのは親プロセスに
限られます。 同じユーザが所有する任意のプロセスへ接続できるようにする
には、0 に設定します。

   echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope

**macOS**

macOS では、プロファイラは "task_for_pid()" で対象プロセスにアクセスし
ます。 次のいずれかが必要です。

* root として実行する

* プロファイラのバイナリが "com.apple.security.cs.debugger" エンタイト
  ルメントを持つ

* システム整合性保護 (SIP) が無効になっている (非推奨)

**Windows**

Windows では、別のプロセスのメモリを読むために、プロファイラに管理者権
限または "SeDebugPrivilege" 特権が必要です。


バージョンの互換性
------------------

プロファイラと対象プロセスは、同じ Python マイナーバージョン (例えば、
どちらも Python 3.15) で実行する必要があります。 Python 3.14 から
Python 3.15 のプロセスに接続することはできません。

プレリリースの Python バージョンには、さらに制限があります。 プロファ
イラか対象のいずれかがプレリリース (アルファ、ベータ、リリース候補) の
場合、両方のバージョンが完全に一致していなければなりません。

フリースレッドの Python ビルドから標準のビルドに接続することや、その逆
の接続はできません。


サンプリングの設定
==================

出力形式や可視化の機能を見ていく前に、サンプリング処理自体の設定方法を
理解しておくことが重要です。 プロファイラには、サンプルを収集する頻度
、プロファイリングを続ける時間、観測するスレッド、各サンプルに記録する
追加情報を制御するオプションがあります。

ほとんどの用途では、デフォルトの設定で十分です。

+---------------------------+-----------------------------------------------------------------------------+
| オプション                | デフォルト                                                                  |
|===========================|=============================================================================|
| "--sampling-rate" / "-r"  | 1 kHz                                                                       |
| のデフォルト              |                                                                             |
+---------------------------+-----------------------------------------------------------------------------+
| "--duration" / "-d" のデ  | 完了まで実行                                                                |
| フォルト                  |                                                                             |
+---------------------------+-----------------------------------------------------------------------------+
| "--all-threads" / "-a" の | メインスレッドのみ                                                          |
| デフォルト                |                                                                             |
+---------------------------+-----------------------------------------------------------------------------+
| "--native" のデフォルト   | "<native>" フレームなし (C コードの時間は呼び出し元に計上)                  |
+---------------------------+-----------------------------------------------------------------------------+
| "--no-gc" のデフォルト    | ガベージコレクションの実行中は "<GC>" フレームを含める                      |
+---------------------------+-----------------------------------------------------------------------------+
| "--mode" のデフォルト     | 実時間モード (すべてのサンプルを記録)                                       |
+---------------------------+-----------------------------------------------------------------------------+
| "--realtime-stats" のデフ | 無効                                                                        |
| ォルト                    |                                                                             |
+---------------------------+-----------------------------------------------------------------------------+
| "--subprocesses" のデフォ | 無効                                                                        |
| ルト                      |                                                                             |
+---------------------------+-----------------------------------------------------------------------------+
| "--blocking" のデフォルト | 無効 (非ブロッキングのサンプリング)                                         |
+---------------------------+-----------------------------------------------------------------------------+


サンプリングレートと計測時間
----------------------------

最も基本的な二つの引数は、サンプリングレートと計測時間です。 この組み
合わせで、プロファイリング中に収集するサンプル数が決まります。

"--sampling-rate" オプション ("-r") は、サンプルを収集する頻度を指定し
ます。 デフォルトは 1 kHz (毎秒 1,000 サンプル) です。

   python -m profiling.sampling run -r 20khz script.py

レートを上げると、より多くのサンプルと細かなデータを得られますが、プロ
ファイラの CPU 使用率が少し高くなります。 レートを下げるとプロファイラ
のオーバーヘッドは減りますが、短時間で終わる関数を見逃す可能性がありま
す。 ほとんどのアプリケーションでは、デフォルトのレートが精度とオーバ
ーヘッドのよいバランスになります。

"--duration" オプション ("-d") は、プロファイルする時間を秒単位で指定
します。 デフォルトでは、対象プロセスが終了するか中断されるまで続けま
す。

   python -m profiling.sampling run -d 60 script.py

計測時間の指定は、長時間動作するプロセスに接続する場合や、特定の時間帯
に限ってプロファイルしたい場合に便利です。 スクリプトをプロファイルす
る場合は、デフォルトの完了まで実行する動作が通常は適しています。


スレッドの選択
--------------

Python プログラムは、"threading" モジュールで明示的に、またはスレッド
プールを管理するライブラリを通して暗黙的に、複数のスレッドを使うことが
よくあります。

デフォルトでは、プロファイラはメインスレッドだけをサンプリングします。
"--all-threads" オプション ("-a") を使うと、プロセス内のすべてのスレッ
ドをサンプリングできます。

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

複数スレッドのプロファイリングでは、スレッド間の処理の分担を確認し、ブ
ロックしているスレッドや実行機会を得られないスレッドを特定できます。
各スレッドのサンプルはまとめて出力され、一部の形式ではスレッドごとに絞
り込めます。 このオプションは、並行処理の問題を調べる場合や、スレッド
プールに処理を分散している場合に特に便利です。


ブロッキングモード
------------------

デフォルトでは、Tachyon は対象プロセスを止めずにメモリを読み取ります。
この非ブロッキング方式は、対象アプリケーションのオーバーヘッドがほぼゼ
ロであるため、ほとんどのプロファイリング用途に適しています。 対象プロ
グラムは通常の速度で動作し、観測されていることを意識せずに実行を続けま
す。

ただし、yield 箇所を高速に切り替えるジェネレーターやコルーチンを多数使
うアプリケーションでは、非ブロッキングのサンプリングで不完全または不整
合なスタックトレースが得られることがあります。 また、コールスタックの
変化が非常に速く、一回のスタック読み取りの開始から終了までの間に関数が
呼び出されて復帰するプログラムでも同様です。 その結果、異なる実行状態
のフレームが混在したスタックや、実際には存在しなかったスタックが再構成
される場合があります。

このような場合には、"--blocking" オプションで、各サンプルの取得中に対
象プロセスを止められます。

   python -m profiling.sampling run --blocking script.py
   python -m profiling.sampling attach --blocking 12345

ブロッキングモードを有効にすると、プロファイラは対象プロセスを一時停止
し、スタックを読み取ってから再開します。 これにより、取得する各スタッ
クが、その瞬間のプロセスの動作を表す実在の整合したスナップショットであ
ることを保証します。 その代わり、一時停止が繰り返されるため、対象プロ
セスの動作は遅くなります。

警告:

  Do not use very high sample rates (low "--interval" values) with
  blocking mode. Suspending and resuming a process takes time, and if
  the sampling interval is too short, the target will spend more time
  stopped than running. For blocking mode, intervals of 1000
  microseconds (1 millisecond) or higher are recommended. The default
  100 microsecond interval may cause noticeable slowdown in the target
  application.

ブロッキングモードは、プロファイルに不整合なスタックが見つかった場合、
特にジェネレーターやコルーチンを多用するコードでのみ使ってください。
ほとんどのアプリケーションでは、デフォルトの非ブロッキングモードで、対
象プロセスに影響を与えずに正確な結果が得られます。


特殊なフレーム
--------------

プロファイラは取得したスタックに人工的なフレームを挿入し、サンプル取得
時にインタープリタが何をしていたかの情報を補足できます。 このフレーム
により、通常は見えない実行の種類を区別できます。

"--native" オプションは、Python が C コード (拡張モジュール、組み込み
関数、インタープリタ自体) を呼び出している箇所に "<native>" フレームを
追加します。

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

このフレームは、Python コードで使った時間とネイティブライブラリで使っ
た時間を区別するのに役立ちます。 このオプションがなければ、ネイティブ
コードの実行時間は、それを呼び出した Python 関数の時間として表示されま
す。 NumPy やデータベースドライバーなどの C 拡張を多用するコードの最適
化に便利です。

デフォルトでは、ガベージコレクションが実行中なら "<GC>" フレームを含め
ます。 "--no-gc" オプションは、このフレームを抑制します。

   python -m profiling.sampling run --no-gc script.py

GC フレームは、ガベージコレクションに多くの時間を使うプログラムの特定
に役立ちます。 そのようなプログラムでは、メモリ割り当てのパターンを最
適化する価値があるかもしれません。 "<GC>" フレームに多くの時間が費やさ
れていたら、オブジェクトの割り当て頻度の調査や、オブジェクトプールの利
用を検討してください。


オペコードを考慮したプロファイリング
------------------------------------

"--opcodes" オプションは、サンプルごとに実行中の Python バイトコード命
令を記録する、命令単位のプロファイリングを有効にします。

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

この機能により、適応的特殊化による最適化を含む Python のバイトコード実
行を確認できます。 "LOAD_ATTR" のような汎用の命令が、実行時に
"LOAD_ATTR_INSTANCE_VALUE" のような効率的な命令へ特殊化されると、プロ
ファイラは特殊化された名前と元の命令の両方を表示します。

オペコードの情報は、いくつかの出力形式に表示されます。

* **フレームグラフ**: フレームにカーソルを合わせると、バイトコード命令
  の内訳を示すツールチップが表示され、その関数内でどのオペコードが時間
  を使ったかが分かります

* **ヒートマップ**: ソース行ごとに展開できるバイトコードのパネルがあり
  、特殊化の割合を含む命令の内訳を表示します

* **ライブモード**: キーボード操作で開けるオペコードパネルに、選択した
  関数の命令単位の統計を表示します

* **Gecko 形式**: オペコードの切り替わりを、Firefox Profiler のタイム
  ライン上の区間マーカーとして出力します

この詳しい情報は、特に次の用途に役立ちます。

* Python の適応的特殊化が性能に与える影響を理解する

* 最適化が有効そうな、実行が集中するバイトコード命令を特定する

* 異なるコードの書き方の効果を命令単位で分析する

* バイトコードのレベルで起こる性能問題をデバッグする

"--opcodes" オプションは、"--live"、"--flamegraph"、"--heatmap"、"--
gecko" 形式と併用できます。 オペコード情報を保存するための追加メモリが
必要で、サンプリングの性能が多少下がる場合もありますが、Python の実行
モデルをこれまでにない詳しさで確認できます。


リアルタイムの統計
------------------

"--realtime-stats" オプションは、プロファイリング中にサンプリングレー
トの統計を表示します。

   python -m profiling.sampling run --realtime-stats script.py

実際に達成したサンプリングレートを表示します。 プロファイラの処理が追
いつかない場合、指定したレートを下回ることがあります。 この統計は、プ
ロファイリングが正しく動作し、十分なサンプルが集まっているかを確認する
のに役立ちます。 指標の読み方は サンプリング効率 を参照してください。


サブプロセスのプロファイリング
------------------------------

"--subprocesses" オプションは、対象が起動したサブプロセスの自動プロフ
ァイリングを有効にします。

   python -m profiling.sampling run --subprocesses script.py
   python -m profiling.sampling attach --subprocesses 12345

有効にすると、プロファイラは対象プロセスによる子プロセスの作成を監視し
ます。 新しい Python 子プロセスを検出すると、それをプロファイルする別
のプロファイラインスタンスを自動的に起動します。 "multiprocessing"、
"subprocess"、"ProcessPoolExecutor" を使う "concurrent.futures"、その
他のプロセス起動機構を利用するアプリケーションで便利です。

worker_pool.py

   from concurrent.futures import ProcessPoolExecutor
   import math

   def compute_factorial(n):
       total = 0
       for i in range(50):
           total += math.factorial(n)
       return total

   if __name__ == "__main__":
       numbers = [5000 + i * 100 for i in range(50)]
       with ProcessPoolExecutor(max_workers=4) as executor:
           results = list(executor.map(compute_factorial, numbers))
       print(f"Computed {len(results)} factorials")

   python -m profiling.sampling run --subprocesses --flamegraph worker_pool.py

これにより、メインプロセスと各ワーカープロセスについて、
"flamegraph_<main_pid>.html"、"flamegraph_<worker1_pid>.html" などの別
々のフレームグラフが生成されます。

サブプロセスごとに出力ファイルが作成されます。 ファイル名は、指定した
出力パス (またはデフォルト) にサブプロセスのプロセス ID を付けて決まり
ます。

* "-o profile.html" を指定すると、サブプロセスの出力は
  "profile_12345.html"、"profile_12346.html" などになります

* デフォルトの出力では、サブプロセスごとに "flamegraph_12345.html" の
  ようなファイルや "heatmap_12345" のようなディレクトリが生成されます

* pstats 形式 (デフォルトの出力先は標準出力) では、サブプロセスの出力
  は "profile_12345.pstats" のようなファイルになります

サブプロセスのプロファイラは、親からほとんどのサンプリングオプションを
引き継ぎます (サンプリングレート、計測時間、スレッドの選択、ネイティブ
フレーム、GC フレーム、非同期処理を考慮するモード、出力形式)。 孫プロ
セスやその先の子孫も含め、すべての Python 子孫プロセスを再帰的にプロフ
ァイルします。

サブプロセスの検出は、対象プロセスの新しい子孫を定期的に探し、新しい各
プロセスのメモリに Python ランタイムの構造があるかを調べて、Python プ
ロセスかどうかを確認します。 シェルコマンドや外部ツールなど、Python 以
外のサブプロセスは無視します。

多数のプロセスを起動するプログラムでリソースが枯渇しないよう、同時に動
作するサブプロセスのプロファイラは 100 個までに制限されています。 上限
に達すると、それ以降のサブプロセスはプロファイルされず、警告を表示しま
す。

"--subprocesses" オプションは "--live" モードと併用できません。 ライブ
モードは対話的な端末インターフェースを使い、複数のプロファイラの同時表
示に対応できないためです。


サンプリング効率
----------------

サンプリング効率の指標は、収集したデータの品質を評価するのに役立ちます
。 プロファイラの端末出力とフレームグラフのサイドバーに表示されます。

**サンプリング効率** は、サンプル取得の試行のうち成功した割合です。 試
行ごとに、対象プロセスのメモリからコールスタックを読み取ります。 コン
テキストスイッチ中やインタープリタの内部構造の更新中など、読み取り時点
でプロセスが不整合な状態にあると、取得に失敗することがあります。 効率
が低い場合、システムの負荷や短すぎる間隔設定により、プロファイラが指定
したサンプリングレートに追いついていない可能性があります。

**取り逃したサンプル** は、予定したサンプルのうち収集できなかった割合
です。 設定した間隔と計測時間から、プロファイラは収集する予定のサンプ
ル数を求めます。 システムの負荷が高い場合など、プロファイラが予定に追
いつけないと、一部のサンプルを取り逃すことがあります。 少数の取り逃し
は通常起こるもので、プロファイルの統計的な精度には大きく影響しません。

どちらの指標も参考情報です。 試行の失敗やサンプルの取り逃しが多少あっ
ても、十分なサンプルを収集できていれば、プロファイルは統計的に有効です
。 プロファイラは実際に取得したサンプル数を報告するので、分析に十分な
データがあるか判断できます。


プロファイリングモード
======================

サンプリングプロファイラは、記録するサンプルを制御する四つのモードに対
応しています。 モードによって、総経過時間、CPU 実行時間、グローバルイ
ンタープリタロックを保持した時間、例外処理のどれを測るかが決まります。


実時間モード
------------

実時間モード ("--mode""=wall") は、スレッドが何をしているかに関係なく
、すべてのサンプルを記録します。 これはデフォルトのモードで、プログラ
ムの実行中にどこで時間が使われたかの全体像を示します。

   python -m profiling.sampling run --mode=wall script.py

実時間モードでは、Python コードの実行中、I/O 待ち、ロック待ち、スリー
プ中のいずれでもサンプルを記録します。 そのため、待ち時間も含めたプロ
グラム全体の時間配分を把握するのに適しています。

プログラムが I/O 処理、ネットワーク呼び出し、スリープに多くの時間を使
う場合、実時間モードはその待ち時間を呼び出し元の関数の時間として表示し
ます。 処理の開始から終了までの遅延を最適化する場合は、まさにこの情報
が必要なことがよくあります。


CPU モード
----------

CPU モード ("--mode""=cpu") は、スレッドが実際に CPU コア上で実行され
ている場合にだけサンプルを記録します。

   python -m profiling.sampling run --mode=cpu script.py

スレッドがスリープ中、I/O 待ち、ロック待ちのときに取得したサンプルは破
棄します。 得られるプロファイルはアイドル時間を除外し、CPU サイクルが
どこで使われたかを示します。

CPU モードは、I/O 待ちの影響を受けずに計算の集中箇所へ注目したい場合に
便利です。 プログラムが計算とネットワーク呼び出しを交互に行う場合、CPU
モードはどの計算部分に最も時間がかかるかを示します。


実時間と CPU のプロファイルの比較
---------------------------------

実時間モードと CPU モードの両方でプロファイルすると、関数が計算に時間
を使っているのか、待機に使っているのかを判断できます。

両方のプロファイルで目立つ関数は、実際に CPU を使う計算の集中箇所です
。 アルゴリズムの改善や、より効率的なコードへの変更を中心に最適化して
ください。

実時間モードでは大きな割合を占めるのに、CPU モードでは小さいか現れない
関数は、I/O が律速になっているか、待機しています。 時間の大半をネット
ワーク、ディスク、ロック、スリープの待機に費やしています。 ここでは
CPU 処理の最適化は役に立たないため、非同期 I/O、接続プール、待ち時間の
削減を検討してください。

   import time

   def do_sleep():
       time.sleep(2)

   def do_compute():
       sum(i**2 for i in range(1000000))

   if __name__ == "__main__":
       do_sleep()
       do_compute()

   python -m profiling.sampling run --mode=wall script.py  # do_sleep は約98%、do_compute は約1%
   python -m profiling.sampling run --mode=cpu script.py   # do_sleep は現れず、do_compute が大半を占めます


GIL モード
----------

GIL モード ("--mode""=gil") は、スレッドが Python のグローバルインター
プリタロックを保持している場合にだけサンプルを記録します。

   python -m profiling.sampling run --mode=gil script.py

The GIL is held only while executing Python bytecode. When Python
calls into C extensions, performs I/O operations, or executes native
code, the GIL is typically released. This means GIL mode effectively
measures time spent running Python code specifically, filtering out
time in native libraries.

マルチスレッドのプログラムでは、GIL モードにより、ほかのスレッドによる
Python バイトコードの実行を妨げているコードが分かります。 同時に GIL
を保持できるスレッドは一つだけなので、GIL モードのプロファイルに頻繁に
現れる関数はインタープリタを独占しています。

GIL モードは、「どの関数が GIL を独占しているか」「なぜほかのスレッド
が実行機会を得られないか」を調べるのに役立ちます。 シングルスレッドの
プログラムでも、Python の実行時間と C 拡張や I/O の時間を区別するのに
役立つ場合があります。

   import hashlib

   def hash_work():
       # C 拡張: 計算中は GIL を解放します
       for _ in range(200):
           hashlib.sha256(b"data" * 250000).hexdigest()

   def python_work():
       # 純粋な Python: 計算中は GIL を保持します
       for _ in range(3):
           sum(i**2 for i in range(1000000))

   if __name__ == "__main__":
       hash_work()
       python_work()

   python -m profiling.sampling run --mode=cpu script.py  # hash_work は約42%、python_work は約38%
   python -m profiling.sampling run --mode=gil script.py  # hash_work は約5%、python_work は約60%


例外モード
----------

例外モード ("--mode=exception") は、スレッドで例外が送出または処理され
ている場合にだけサンプルを記録します。

   python -m profiling.sampling run --mode=exception script.py

サンプルが記録されるのは、例外がコールスタックを上へ伝播している間
("raise" の後、捕捉される前) と、例外情報がスレッド状態に残っている
"except" ブロック内のコードを実行している間の二つの場合です。

次の例で、どのコード領域が記録されるかを示します。

   def example():
       try:
           raise ValueError("error")    # 記録対象: 例外の送出中
       except ValueError:
           process_error()              # 記録対象: except ブロック内
       finally:
           cleanup()                    # 対象外: 例外は処理済み

   def example_propagating():
       try:
           try:
               raise ValueError("error")
           finally:
               cleanup()                # 記録対象: 例外が伝播中
       except ValueError:
           pass

   def example_no_exception():
       try:
           do_work()
       finally:
           cleanup()                    # 対象外: 例外なし

"finally" ブロックが記録されるのは、そのブロックを例外が伝播している場
合だけです。 "except" ブロックの実行が終わると、Python は後続の
"finally" ブロックを実行する前に例外情報を消去します。 同様に、例外が
発生していない通常の実行で通る "finally" ブロックは、例外状態がないた
め記録されません。

このモードは、プログラムがエラー処理のどこに時間を使っているかを把握す
るのに便利です。 イテレーターの "StopIteration" のように例外で処理の流
れを制御するコードや、接続失敗を処理するネットワークサーバのように多く
のエラー状態を扱うアプリケーションでは、例外処理が大きなオーバーヘッド
の原因になることがあります。

例外モードは、「例外処理にどれだけ時間を使っているか」「どの例外ハンド
ラーに最も時間がかかるか」を調べるのに役立ちます。 例外を適切に処理し
ている場合でも、多数の例外を捕捉して処理するコードに隠れた性能上のコス
トを明らかにできます。 例えば、解析ライブラリが内部で形式の誤りを例外
で通知する場合、呼び出し側に例外が見えなくても、このモードはハンドラー
内で使った時間を記録します。


出力形式
========

プロファイラは、分析の進め方に応じた複数の形式で出力できます。 コマン
ドラインのフラグで形式を選び、その形式に応じて標準出力、ファイル、ディ
レクトリのいずれかへ出力します。


pstats 形式
-----------

pstats 形式 ("--pstats") は、決定論的プロファイラの出力に似たテキスト
の表を生成します。 これがデフォルトの出力形式です。

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

   [画像: Tachyon の pstats 形式の端末出力][画像]pstats 形式は、処理の
   集中する関数、サンプル数、推定時間を色分けした表で表示します。

デフォルトでは標準出力に表示します。

   Profile Stats (Mode: wall):
        nsamples  sample%    tottime (ms)  cumul%   cumtime (ms)  filename:lineno(function)
          234/892    11.7%       234.00     44.6%       892.00    server.py:145(handle_request)
          156/156     7.8%       156.00      7.8%       156.00    <built-in>:0(socket.recv)
           98/421     4.9%        98.00     21.1%       421.00    parser.py:67(parse_message)

各列はサンプル数と推定時間を示します。

* **nsamples**: "direct/cumulative" の形で表示します (例えば "10/50")
  。 直接サンプルは、その関数がスタックの最上部にあり、実行中だったサ
  ンプルです。 累積サンプルは、その関数がスタックのどこかに現れたサン
  プルで、呼び出し先の関数を待っていた場合も含みます。 "10/50" なら、
  10 個のサンプルでその関数自身が実行中であり、合計 50 個のサンプルで
  コールスタックに存在していたことを表します。

* **sample%** と **cumul%**: 総サンプル数に対する、直接サンプル数と累
  積サンプル数それぞれの割合です。

* **tottime** と **cumtime**: サンプル数とプロファイリングの計測時間に
  基づく実時間の推定値です。 時間の単位は値の大きさに応じて自動的に選
  ばれ、大きな値には秒、中程度にはミリ秒、小さな値にはマイクロ秒を使い
  ます。

出力には、各列の説明と、次の特徴を持つ関数を取り上げた概要が含まれます
。

* **Hot spots**: Functions with high direct/cumulative sample ratio
  (ratio close to 1.0). These functions spend most of their time
  executing their own code rather than waiting for callees. High
  ratios indicate where CPU time is actually consumed.

* **間接呼び出し**: 累積サンプル数と直接サンプル数の差が大きい関数です
  。 処理をほかの関数に委ねて全体を調整する関数で、スタックには頻繁に
  現れますが、最上部に現れることはまれです。

* **呼び出しの倍率**: 累積サンプル数が直接サンプル数を大きく上回る関数
  です (累積/直接の倍率が高い関数)。 多くの呼び出しの連鎖の深い位置に
  現れ、頻繁に入れ子になる関数です。

"--no-summary" を指定すると、列の説明と概要の両方を非表示にします。

pstats の出力を標準出力ではなくバイナリファイルに保存するには、次のよ
うにします。

   python -m profiling.sampling run -o profile.pstats script.py

pstats 形式には、表示を制御するオプションがいくつかあります。 "--sort"
オプションは、結果の並べ替えに使う列を指定します。

   python -m profiling.sampling run --sort=tottime script.py
   python -m profiling.sampling run --sort=cumtime script.py
   python -m profiling.sampling run --sort=nsamples script.py

"--limit" オプションは、出力を上位 N 件に制限します。

   python -m profiling.sampling run --limit=30 script.py

"--no-summary" オプションは、統計表の前に表示するヘッダーの概要を抑制
します。


集約スタック形式
----------------

集約スタック形式 ("--collapsed") は、異なるコールスタックごとに一行を
出力し、そのスタックが何回サンプリングされたかを併記します。

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

出力は次のようになります。

   main;process_data;parse_json;decode_utf8 42
   main;process_data;parse_json 156
   main;handle_request;send_response 89

各行には、コールスタックを下から上へ表す、セミコロンで区切った関数名が
並び、その後にスペースとサンプル数が続きます。 この形式は、外部のフレ
ームグラフツール、特に Brendan Gregg の "flamegraph.pl" スクリプトとの
互換性を目的としています。

集約スタックからフレームグラフを生成するには、次のようにします。

   python -m profiling.sampling run --collapsed script.py > stacks.txt
   flamegraph.pl stacks.txt > profile.svg

生成した SVG はどの Web ブラウザーでも表示でき、特定の呼び出し経路をク
リックして拡大できる対話的な可視化になっています。


フレームグラフ形式
------------------

フレームグラフ形式 ("--flamegraph") は、対話的なフレームグラフを含む、
単独で動作する HTML ファイルを生成します。

   python -m profiling.sampling run --flamegraph script.py
   python -m profiling.sampling run --flamegraph -o profile.html script.py

   [画像: Tachyon の対話的なフレームグラフ][画像]フレームグラフは、コ
   ールスタックを入れ子の長方形で示し、幅は費やした時間に比例します。
   サイドバーには、実行時の統計、GIL の指標、処理の集中する関数を表示
   します。

If no output file is specified, the profiler generates a filename
based on the process ID (for example, "flamegraph.12345.html").

生成した HTML ファイルは外部の依存関係を必要とせず、Web ブラウザーで直
接開けます。 コールスタックは入れ子の長方形で表示され、幅は費やした時
間に比例します。 長方形にカーソルを合わせると、前後のソースコードを含
む関数の詳細が表示され、クリックすると呼び出しツリーのその部分を拡大し
ます。

フレームグラフのインターフェースには、次の機能があります。

* プロファイルの概要、スレッドの統計、サンプリング効率の指標 (サンプリ
  ング効率 を参照)、処理が集中する上位の関数を表示するサイドバー

* 関数名と "file.py:42" のような行指定の両方に対応する検索機能

* ドロップダウンによるスレッドごとの絞り込み

* ダーク/ライトテーマの切り替え (設定はセッション間で保存されます)

* 現在の表示を保存する SVG エクスポート

スレッド統計の欄には、実行時の動作に関する指標を表示します。

* **GIL Held**: percentage of samples where a thread held the global
  interpreter lock (actively running Python code)

* **GIL Released**: percentage of samples where no thread held the GIL

* **Waiting GIL**: スレッドが GIL の取得を待っていたサンプルの割合

* **GC**: ガベージコレクション中のサンプルの割合

これらの統計は、GIL の競合を特定し、Python の実行、ネイティブコード、
待機の間でどのように時間が使われているかを理解するのに役立ちます。

フレームグラフは、深いコールスタックの特定と、時間消費の階層構造の把握
に特に効果的です。 上部の幅広い長方形は、自身の処理または呼び出し先の
処理を通して、多くの時間を使う関数を示します。


差分フレームグラフ
~~~~~~~~~~~~~~~~~~

差分フレームグラフは二回のプロファイリング結果を比較し、性能が変化した
箇所を強調します。 コード変更による性能低下を特定したり、最適化が意図
した効果を得られたか確認したりするのに役立ちます。

   # 基準となるプロファイルを取得します
   python -m profiling.sampling run --binary -o baseline.bin script.py

   # コード変更後に差分フレームグラフを生成します
   python -m profiling.sampling run --diff-flamegraph baseline.bin -o diff.html script.py

現在のプロファイルを、現在の時間消費を表すフレーム幅で描き、基準と比べ
た各関数の変化を色で示します。

**色分け**:

* **赤**: 時間が増えた関数 (性能低下)。 薄い色は小さな増加、濃い色は大
  幅な性能低下を表します。

* **青**: 時間が減った関数 (改善)。 薄い色は小さな削減、濃い色は大幅な
  高速化を表します。

* **灰色**: 変化がほとんどないか、まったくありません。

* **紫**: 基準のプロファイルにはなかった新しい関数です。

フレームの色は、呼び出し先を含む累積時間ではなく、**直接時間** (その関
数がスタックの最上部で実行されていた時間) の変化を示します。 フレーム
にカーソルを合わせると、基準の時間、現在の時間、変化率などの比較の詳細
を表示します。

プロファイル間で、呼び出し経路が完全になくなることがあります。 これは
**消えたスタック** (elided stacks) と呼ばれ、最適化でコード経路が除去
された場合や、特定の分岐が実行されなくなった場合に生じます。 消えたス
タックがあると切り替えボタンが現れ、通常の差分表示と、削除された経路だ
けを紫色で示す表示を切り替えられます。


Gecko 形式
----------

Gecko 形式 ("--gecko") は、Firefox Profiler と互換性のある JSON を出力
します。

   python -m profiling.sampling run --gecko script.py
   python -m profiling.sampling run --gecko -o profile.json script.py

Firefox Profiler は、もともと Firefox 自体をプロファイルするために作ら
れた、高機能な Web ベースのツールです。 基本的なフレームグラフ以外にも
、タイムライン表示、呼び出しツリーの探索、マーカーの可視化などの機能を
提供します。 詳しい使い方は Firefox Profiler のドキュメント を参照して
ください。

出力を使うには、ブラウザーで Firefox Profiler を開き、JSON ファイルを
読み込みます。 プロファイラはすべてクライアント側で動作するため、プロ
ファイリングデータがマシンの外へ送られることはありません。

Gecko 形式は、GIL の状態や CPU の動作に関する追加のメタデータを自動で
収集し、Python のスレッドモデルに特有の分析を可能にします。 プロファイ
ラは区間マーカーを出力し、Firefox Profiler のタイムライン上で色付きの
帯として表示します。

* **GIL マーカー**: スレッドがグローバルインタープリタロックを保持また
  は解放している期間を示します

* **CPU マーカー**: スレッドが CPU 上で実行中か、アイドル状態かを示し
  ます

* **コード種別マーカー**: Python コードとネイティブコード (C 拡張) を
  区別します

* **GC マーカー**: ガベージコレクションの動作を示します

このため、Gecko 形式では "--mode" オプションを使えません。 関連するデ
ータはすべて自動で取得します。

   [画像: Firefox Profiler の Call Tree ビュー][画像]Call Tree ビュー
   は、サンプル数と割合とともに呼び出し階層全体を表示します。 サイドバ
   ーには、実行時間やサンプルの分布など、選択した関数の詳しい統計を表
   示します。

   [画像: Firefox Profiler の Flame Graph ビュー][画像]Flame Graph ビ
   ューは、コールスタックを入れ子の長方形で表示します。 呼び出し階層の
   中に関数名が表示されます。

   [画像: オペコードを含む Firefox Profiler の Marker Chart][画像
   ]Marker Chart は、CPU の状態、GIL の状態、オペコードなどの区間マー
   カーを表示します。 "--opcodes" を有効にすると、
   "BINARY_OP_ADD_FLOAT"、"CALL_PY_EXACT_ARGS"、"CALL_LIST_APPEND" な
   どのバイトコード命令が、時間に沿った実行を示すマーカーとして現れま
   す。


ヒートマップ形式
----------------

ヒートマップ形式 ("--heatmap") は、ソース行単位のサンプル数を表示する
、対話的な HTML の可視化を生成します。

   python -m profiling.sampling run --heatmap script.py
   python -m profiling.sampling run --heatmap -o my_heatmap script.py

   [画像: Tachyon のヒートマップ表示][画像]ヒートマップは、ソースコー
   ドにサンプル数を直接重ねて表示します。 サンプルが少ない行は寒色、多
   い行は暖色で色分けします。 移動ボタン (▲▼) で呼び出し元と呼び出し先
   の間を移動できます。

単一ファイルを生成するほかの形式と異なり、ヒートマップ出力は、プロファ
イルした各ソースファイルの HTML ファイルを含むディレクトリを作成します
。 出力パスを指定しなければ、ディレクトリ名は "heatmap_PID" になります
。

ヒートマップはソースコードを表示し、各行で収集したサンプル数を色の濃淡
で示します。 サンプルの多い行は暖色、少ない行やサンプルのない行は寒色
で表示します。 この表示は、時間の消費につながっているコードの行を正確
に特定するのに役立ちます。

ヒートマップのインターフェースには、いくつかの対話的な機能があります。

* **色分けモード**: "Self Time" (直接の実行時間) と "Total Time" (呼び
  出し先の関数の時間を含む累積時間) を切り替えます

* **サンプルのないコードの絞り込み**: すべての行を表示するか、サンプル
  のある行だけを表示するかを選べます

* **呼び出しグラフの移動**: 各行の移動ボタン (呼び出し元は ▲、呼び出し
  先は ▼) で、コード内の実行経路をたどれます。 その行を呼び出した関数
  、またはその行から呼び出した関数が複数ある場合は、サンプル数付きの一
  覧メニューが表示されます。

* **スクロール用ミニマップ**: ファイル全体でサンプルが集中する箇所を示
  す縦長の概観表示

* **階層型インデックス**: 種類別 (stdlib、site-packages、project) に整
  理したファイル一覧と、フォルダーごとの合計サンプル数

* **ダーク/ライトテーマ**: テーマを切り替えられ、設定はセッション間で
  保存されます

* **行へのリンク**: 行番号をクリックすると、共有できる URL を作成しま
  す

"--opcodes" でオペコード単位のプロファイリングを有効にすると、サンプル
のある各行を展開し、どのバイトコード命令が時間を使ったかを確認できます
。

   [画像: バイトコードパネルを展開したヒートマップ][画像]サンプルの多
   い行を展開すると、特殊化された命令も含め、実行されたバイトコード命
   令が表示されます。 パネルには、命令ごとのサンプル数と、その行全体の
   特殊化の割合を示します。

ヒートマップは、性能の問題があるファイルは分かっていて、具体的な行を特
定したい場合に特に便利です。 ソースコードに直接対応していて読み取りや
移動が簡単なため、この形式を好む開発者も多くいます。 小さなスクリプト
や範囲を絞った分析では、階層的な可視化を読み解かなくても、時間を費やし
ている箇所を直感的に把握できます。


バイナリ形式
------------

バイナリ形式 ("--binary") は、プロファイリングデータを効率的に保存する
コンパクトなバイナリファイルを生成します。

   python -m profiling.sampling run --binary -o profile.bin script.py
   python -m profiling.sampling attach --binary -o profile.bin 12345

"--compression" オプションは、データの圧縮を制御します。

* "auto" (デフォルト): zstd 圧縮を利用できれば使い、そうでなければ圧縮
  しません

* "zstd": zstd 圧縮を必ず使います ("compression.zstd" のサポートが必要
  です)

* "none": 圧縮を無効にします

   python -m profiling.sampling run --binary --compression=zstd -o profile.bin script.py

バイナリ形式のプロファイルを分析するには、replay コマンド でフレームグ
ラフや pstats などの別形式に変換します。


記録と再生を使った作業手順
==========================

バイナリ形式と replay コマンドを組み合わせると、データの取得と分析を分
けた、記録・再生による作業ができます。 プロファイリング中に可視化を生
成する代わりに、生データをコンパクトなバイナリファイルに保存し、後から
さまざまな形式に変換します。

この方法には、主に三つの利点があります。

* 可視化用のデータ構造を作る処理を再生時まで遅らせるため、サンプリング
  が速くなります。

* 一度取得したバイナリデータを、再度プロファイリングせずに複数の形式へ
  変換できます。 概要を手早く確認する pstats、視覚的に探索するフレーム
  グラフ、行単位の詳細を見るヒートマップを使い分けられます。

* バイナリファイルは小さく、同僚と共有しやすい形式です。 受け取った人
  は好みの形式に変換できます。

典型的な作業手順は次のとおりです。

   # 本番環境やテスト中にプロファイルを取得します
   python -m profiling.sampling attach --binary -o profile.bin 12345

   # 後から別の形式で分析します
   python -m profiling.sampling replay profile.bin
   python -m profiling.sampling replay --flamegraph -o profile.html profile.bin
   python -m profiling.sampling replay --heatmap -o heatmap profile.bin


ライブモード
============

ライブモード ("--live") は、システムのプロセスを表示する "top" コマン
ドと同じように、端末上でプロファイリングデータをリアルタイムに表示しま
す。

   python -m profiling.sampling run --live script.py
   python -m profiling.sampling attach --live 12345

   [画像: 全スレッドを表示する Tachyon のライブモード][画像]

ライブモードはプロファイリング統計をリアルタイムに表示し、マルチスレッ
ドのアプリケーションでは複数スレッドのデータをまとめて示します。

新しいサンプルが届くたびに表示を更新し、その時点で処理が最も集中する関
数を示します。 このモードには "curses" モジュールが必要で、Unix 系のシ
ステムでは使えますが、Windows では使えません。 端末は幅 60 桁、高さ 12
行以上が必要です。 大きな端末では、より多くの列を表示します。

ヘッダーには、処理が集中する上位 3 関数、サンプリング効率の指標、スレ
ッド状態の統計 (GIL 保持率、CPU 使用率、GC 時間) を表示します。 主な表
には関数の統計を表示し、現在のソート対象の列を矢印 (▼) で示します。

"--opcodes" を有効にすると、表の下にオペコードパネルが追加され、現在選
択している関数の命令単位の統計を表示します。 このパネルには、特殊化さ
れた命令とその元のオペコードも含め、どのバイトコード命令が最も頻繁に実
行されているかを表示します。

   [画像: オペコードパネル付きの Tachyon ライブモード][画像]

"--opcodes" を有効にしたライブモードでは、選択した関数のバイトコード命
令の内訳をオペコードパネルに表示します。


キーボード操作
--------------

ライブモードでは、キーボードで表示を操作します。

"q"
   プロファイラを終了してシェルに戻ります。

"s" / "S"
   ソート順を順方向または逆方向に切り替えます (サンプル数、割合、合計
   時間、累積割合、累積時間)。

"p"
   表示の更新を一時停止または再開します。 表示を止めてもバックグラウン
   ドでサンプリングを続けるので、データ収集を止めずに画面を固定して結
   果を確認できます。

"r"
   すべての統計をリセットして、最初から収集し直します。 誤ってデータを
   失わないよう、プロファイリング終了後は無効になります。

"/"
   フィルターモードに入り、名前で関数を検索します。 ファイル名と関数名
   に対して、大文字・小文字を区別しない部分文字列の一致で絞り込みます
   。 パターンを入力して Enter で適用し、Escape で取り消します。 glob
   パターンや正規表現には対応していません。

"c"
   現在のフィルターを解除し、すべての関数を再表示します。

"t"
   全スレッドをまとめた表示と、スレッドごとの統計表示を切り替えます。
   スレッドごとの表示では、"1/4" のようなカウンターで、全スレッド中の
   現在の位置を示します。

"←" "→" または "↑" "↓"
   スレッドごとの表示で、スレッド間を移動します。 最後のスレッドの次は
   先頭に戻り、逆方向も同様に循環します。

"+" / "-"
   表示の更新頻度を上げるか下げます。 更新間隔は 0.05 秒 (20 Hz、応答
   が非常に速い) から 1.0 秒 (1 Hz、オーバーヘッドが小さい) の範囲です
   。 更新頻度が高いほど CPU を多く使います。 デフォルトは 0.1 秒 (10
   Hz) です。

"x"
   時間とともに各関数への処理の集中が増えているか減っているかを示す、
   傾向表示を切り替えます。 有効にすると、前回の更新と比較して増えた指
   標を緑、減った指標を赤で表示します。

"h" または "?"
   使えるすべての操作をまとめたヘルプ画面を表示します。

"j" / "k" (または "Up" / "Down")
   オペコードパネル内の項目を移動します ("--opcodes" が有効な場合)。
   現在選択している関数の命令単位の統計をスクロールします。

計測時間の終了や対象プロセスの終了によってプロファイリングが完了すると
、"PROFILING COMPLETE" という表示とともに最終結果を固定します。 "q" で
終了するまでは、移動、ソート、絞り込みを引き続き行えます。

ライブモードは、ファイルを出力する代わりに対話的な端末インターフェース
を使うため、出力形式のオプション ("--collapsed"、"--flamegraph" など)
とは併用できません。


非同期処理を考慮したプロファイリング
====================================

"asyncio" を使うプログラム向けに、プロファイラは非同期処理を考慮するモ
ード ("--async-aware") を提供しています。 生の Python フレームではなく
、タスクの構造に基づいてコールスタックを再構成します。

   python -m profiling.sampling run --async-aware async_script.py

非同期コードを通常の方法でプロファイルすると、物理的なコールスタックに
、コルーチンの論理的な流れではなくイベントループの内部処理が現れるため
、分かりにくくなることがあります。 非同期処理を考慮するモードは、実行
中のタスクを追跡し、"await" の連鎖を反映したスタックを表示することで対
処します。

   import asyncio

   async def fetch(url):
       await asyncio.sleep(0.1)
       return url

   async def main():
       for _ in range(50):
           await asyncio.gather(fetch("a"), fetch("b"), fetch("c"))

   if __name__ == "__main__":
       asyncio.run(main())

   python -m profiling.sampling run --async-aware --flamegraph -o out.html script.py

注釈:

  非同期処理を考慮したプロファイリングには、対象プロセスで "asyncio"
  モジュールを読み込んでおく必要があります。 スクリプトが asyncio をイ
  ンポートする前にプロファイルすると、このモードではタスク情報を取得で
  きません。


非同期モード
------------

"--async-mode" オプションは、プロファイルに表示するタスクを制御します
。

   python -m profiling.sampling run --async-aware --async-mode=running async_script.py
   python -m profiling.sampling run --async-aware --async-mode=all async_script.py

"--async-mode""=running" (デフォルト) では、現在 CPU 上で実行中のタス
クだけをプロファイルします。 プログラムが実際に時間を使っている箇所を
示し、性能分析では通常このモードを選びます。

"--async-mode""=all" では、I/O、ロック、ほかのタスクを待って中断してい
るタスクも含めます。 プログラムが何を待っているかを理解するのに便利で
すが、中断中のすべてのタスクが各サンプルに現れるため、プロファイルは大
きくなります。


タスクマーカーとスタックの再構成
--------------------------------

非同期処理を考慮したプロファイルには、asyncio タスク間の境界を示す
"<task>" フレームが現れます。 これは、タスク構造を示すためにプロファイ
ラが挿入する人工的なフレームです。 このフレームでは、関数名としてタス
ク名を表示します。

When a task awaits another task, the profiler reconstructs the logical
call chain by following the "await" relationships. Only "leaf" tasks
(tasks that no other task is currently awaiting) generate their own
stack entries. Tasks being awaited by other tasks appear as part of
their awaiter's stack instead.

あるタスクを複数のタスクが待っている場合 (タスクのグラフがひし形になる
場合)、プロファイラは一定の規則で親を一つ選び、"MyTask (2 parents)" の
ようにタスクマーカーへ親の数を付記します。 これは、別の実行経路もある
ものの、このスタックには表示していないことを示します。


オプションの制約
----------------

非同期処理を考慮するモードは異なるスタック再構成機構を使うため、"--
native"、"--no-gc"、"--all-threads"、"--mode""=cpu"、"--mode""=gil" と
は併用できません。


コマンドライン・インターフェース
================================

コマンドラインインターフェース全体のリファレンスです。


共通オプション
--------------

run

   Python スクリプトまたはモジュールを実行し、プロファイルします。

attach

   PID を指定して実行中のプロセスに接続し、プロファイルします。

dump

   実行中のプロセスの Python スタックのスナップショットを一回だけ表示
   します。

replay

   バイナリのプロファイルファイルを別の出力形式へ変換します。


dump のオプション
-----------------

次のオプションは "dump" サブコマンドに適用されます。

-a, --all-threads

   メインスレッドだけでなく、対象プロセスのすべてのスレッドをダンプし
   ます。

--native

   Python 以外のコードについて "<native>" フレームを含めます。

--no-gc

   ガベージコレクションの実行を示す "<GC>" フレームを除外します。

--opcodes

   利用できる場合、バイトコードのオペコード名を表示します。

--async-aware

   asyncio アプリケーションについて、"await" の境界をまたいでスタック
   を再構成します。

--async-mode <mode>

   非同期スタックのモードです。 "running" (実行中のタスクのみ) または
   "all" (待機中も含むすべてのタスク) を指定します。 "dump" のデフォル
   トは "all" です。 "--async-aware" が必要です。

--blocking

   スタックを読み取る間、対象プロセスのすべてのスレッドを一時停止しま
   す。


サンプリングのオプション
------------------------

-r <rate>, --sampling-rate <rate>

   サンプリングレートです ("10000"、"10khz"、"10k" など)。 デフォルト
   は "1khz" です。

-d <seconds>, --duration <seconds>

   プロファイリングの計測時間を秒単位で指定します。 デフォルトでは完了
   まで実行します。

-a, --all-threads

   メインスレッドだけでなく、すべてのスレッドをサンプリングします。

--realtime-stats

   プロファイリング中にサンプリングの統計を表示します。

--native

   Python 以外のコードについて "<native>" フレームを含めます。

--no-gc

   ガベージコレクションを示す "<GC>" フレームを除外します。

--async-aware

   asyncio プログラムについて、非同期処理を考慮したプロファイリングを
   有効にします。

--opcodes

   Gather bytecode opcode information for instruction-level profiling.
   Shows which bytecode instructions are executing, including
   specializations. Compatible with "--live", "--flamegraph", "--
   heatmap", and "--gecko" formats only.

--subprocesses

   サブプロセスもプロファイルします。 サブプロセスごとにプロファイライ
   ンスタンスと出力ファイルを作成します。 "--live" とは併用できません
   。

--blocking

   各サンプルの取得中に対象プロセスを一時停止します。 対象の実行は遅く
   なりますが、整合したスタックトレースが得られます。 影響を抑えるため
   、長めの間隔 (1000 µs 以上) で使ってください。 詳しくは ブロッキン
   グモード を参照してください。


モードのオプション
------------------

--mode <mode>

   サンプリングモードです。 "wall" (デフォルト)、"cpu"、"gil"、
   "exception" を指定できます。 "cpu"、"gil"、"exception" モードは "--
   async-aware" と併用できません。

--async-mode <mode>

   非同期プロファイリングのモードです。 "running" (デフォルト) または
   "all" を指定します。 "--async-aware" が必要です。


出力のオプション
----------------

--pstats

   pstats の統計を生成します。 これがデフォルトです。 標準出力にはテキ
   ストの表を表示し、"-o" を指定するとバイナリの pstats ファイルを出力
   します。

--collapsed

   外部のフレームグラフツール向けに集約スタック形式を生成します。

--flamegraph

   単独で動作する HTML のフレームグラフを生成します。

--diff-flamegraph <baseline.bin>

   基準となるバイナリのプロファイルと比較した差分フレームグラフを生成
   します。

--gecko

   Firefox Profiler 用の Gecko JSON 形式を生成します。

--heatmap

   行単位のサンプル数を示す HTML ヒートマップを生成します。

--binary

   後から "replay" コマンドで変換するための、高速に処理できるバイナリ
   形式を生成します。

--compression <type>

   バイナリ形式の圧縮方式です。 "auto" (zstd が使えれば使う、デフォル
   ト)、"zstd"、"none" を指定します。

-o <path>, --output <path>

   出力ファイルまたはディレクトリのパスです。 デフォルトの動作は形式に
   よって異なります。 "--pstats" は標準出力にテキストの表を表示し、
   "-o" を指定するとバイナリの pstats ファイルを書き込みます。 その他
   の形式では "<format>_<PID>.<ext>" という名前のファイルを生成します
   (例えば "flamegraph_12345.html")。 "--heatmap" は "heatmap_<PID>"
   というディレクトリを作成します。

--browser

   HTML 出力 ("--flamegraph" と "--heatmap") の生成後に、デフォルトの
   Web ブラウザーで自動的に開きます。 "--subprocesses" でプロファイル
   する場合、ブラウザーを開くのはメインプロセスだけで、サブプロセスの
   出力を自動で開くことはありません。


pstats の表示オプション
-----------------------

これらのオプションは pstats 形式の出力にだけ適用されます。

--sort <key>

   ソート順です。 "nsamples"、"tottime"、"cumtime"、"sample-pct"、
   "cumul-pct"、"nsamples-cumul"、"name" を指定できます。 デフォルトは
   "nsamples" です。

-l <count>, --limit <count>

   表示する項目数の上限です。 デフォルトは 15 です。

--no-summary

   出力から Legend (列の説明) と Summary of Interesting Functions (注
   目する関数の概要) の節を省略します。


run コマンドのオプション
------------------------

-m, --module

   対象をスクリプトのパスではなく、モジュール名として扱います。

--live

   バッチ処理のプロファイリングの代わりに、対話的な端末インターフェー
   スを起動します。

参考:

  "profiling"
     Python のプロファイリングツールの概要と、プロファイラの選び方。

  "profiling.tracing"
     正確な呼び出し回数と時間を取得する決定論的トレースプロファイラ。

  "pstats"
     プロファイルデータの統計分析。

  Firefox Profiler
     Gecko 形式の出力を読み込める、Web ベースのプロファイラです。 詳し
     い使い方は ドキュメント を参照してください。

  FlameGraph
     集約スタック形式からフレームグラフを生成するツール。
