プログラムを高速化するためには、性能を計測する必要があります。最もシンプルな性能計測方法は、処理の前後にタイマー(C++ であれば std::chrono など)を埋め込む方法で、古典的ながら多くの場合はこれで事足ります。しかし、次のような場合には、タイマー埋め込みだけでは不十分です。
- 命令数やサイクル数を計測して、理論性能と比較したい
- キャッシュヒット率や分岐予測ミスの発生率など、性能改善の手がかりとなる情報を集めたい
Linux であれば、こうした情報は perf コマンドで取得できます。例えば、プログラムのサイクル数、実行命令数、L1 データキャッシュミス数を知りたければ、次のように実行すればよいです。
|
|
しかし、perf stat はプログラム全体の情報を集計するため、特定のコード区間のプロファイリングには向いていません。
例えば、次のようなプログラムで compute_kernel() 単体の性能を計測したくても、initialize() の分まで集計されてしまいます。
|
|
そこで作成したのが、プログラム中の特定のコード区間だけを対象にパフォーマンスイベントを計測するライブラリ perf-counter です。
本稿では、perf-counter の基本的な使い方を紹介します。より実践的な使い方や内部実装については、別の記事で扱う予定です。
perf-counter の使い方
perf-counter で特定のコード区間の性能を計測する手順は、次の 4 ステップから成ります。
- 計測したいイベントを指定してカウンタを開く
- カウンタを有効化する
- 対象のコード区間の前後でカウンタの値を読む
- カウンタを無効化・解放する
例えば compute_kernel() の実行にかかるサイクル数は次のようにして計測できます。
|
|
perf_counter_open_by_id(event_type, event_config, group_fd) の各引数の意味は次の通りです。
event_typeとevent_config:perf_event_open(2) に掲載されているtypeとconfigの組み合わせを使用して、計測するイベントを指定します。group_fd:単体のイベントを計測する場合は-1を指定します。複数のイベントを同時に計測する場合は、最初のカウンタを-1で開き、他のカウンタには最初のカウンタのfdを指定します。
event_type と event_config の組み合わせには例えば次のようなものがあります。
event_type |
event_config |
計測内容 |
|---|---|---|
PERF_TYPE_HARDWARE |
PERF_COUNT_HW_CPU_CYCLES |
サイクル数 |
PERF_TYPE_HARDWARE |
PERF_COUNT_HW_INSTRUCTIONS |
実行命令数 |
PERF_TYPE_HARDWARE |
PERF_COUNT_HW_BRANCH_INSTRUCTIONS |
分岐命令数 |
PERF_TYPE_HARDWARE |
PERF_COUNT_HW_BRANCH_MISSES |
分岐予測ミス数 |
PERF_TYPE_SOFTWARE |
PERF_COUNT_SW_PAGE_FAULTS |
ページフォルト数 |
イベント名を指定してカウンタを開く
サイクル数のような基本的なイベントは単一の定数で指定できますが、イベントによっては複数の定数を組み合わせて event_config を構築する必要があります。例えば L1 データキャッシュミスを計測したい場合、次のようなコードを書くことになり、なかなか面倒です。
|
|
そこで便利なのが perf_counter_open_by_name() です。この関数は libpfm4 がインストールされた環境で perf-counter をビルドすると使えるようになり、イベント名でカウンタを開けます。
L1 データキャッシュミスであれば、次のように指定できます。
|
|
指定可能なイベント名の一覧は、perf-counter に付属の list_all_events.c で取得できます。
ビルド方法は後述の「ビルドと実行」を参照してください。
|
|
出力の各行はイベント名とその説明から成ります。
...
perf::L1-DCACHE-LOADS # L1 cache load accesses
perf::L1-DCACHE-LOAD-MISSES # L1 cache load misses
...
perf_counter_open_by_name() を使うと、プロセッサ固有のイベントも簡単に計測できます。
例えば、AMD Ryzen 7 4700U (Zen 2) では、次のようにして SSE/AVX の浮動小数点乗算の回数を計測するイベントを開くことができます。
|
|
FMA 命令の性能を計測する例
perf-counter を用いた性能計測の実例として、AVX2 の FMA(Fused Multiply-Add)命令のレイテンシとスループットを算出してみます。perf-counter で命令列の実行にかかるサイクル数を計測し、命令数との比を計算します。
完全なコードは perf_avx2_fma.cpp を参照してください。
計測方法
レイテンシの計測は、依存関係のある FMA 命令をたくさん並べることで行います。前の命令が終わるまで次の命令が実行できないため、1命令ずつ逐次的に実行されます。経過サイクル数を命令数で割れば、1つの命令の実行に何サイクルかかるか(CPI)というレイテンシが計測できます。
|
|
レイテンシの計測
スループットの計測は、依存関係のない FMA 命令をたくさん並べることで行います。各命令は互いに独立しているため、並列に実行できます。命令数を経過サイクル数で割れば、サイクルあたり同時に何命令実行できるか(IPC)というスループットが計測できます。
|
|
スループットの計測
いずれの計測も3回のウォームアップ実行の後に10回実行し、最小値を採用することにします。これは OS のスケジューリングや割り込みによるノイズを排除するためです。
ビルドと実行
サンプルプログラムは -DPERF_COUNTER_BUILD_EXAMPLES=ON を付けることでビルドされます。
|
|
実行結果
実行環境:AMD Ryzen 7 4700U (Zen 2)、GCC 15.2.1
AVX2 vfmadd231ps - Latency
Instructions : 1200000
Best Cycles : 6000062
CPI : 5.000
IPC : 0.200
AVX2 vfmadd231ps - Throughput
Instructions : 1200000
Best Cycles : 600066
CPI : 0.500
IPC : 2.000
レイテンシ 5.000 サイクル/命令、スループット 2.000 命令/サイクルという結果が得られました。 これらはuops.infoの値と一致しています。