Profile-Guided Optimization (PGO)
コンパイラが静的なヒューリスティックだけに頼るのではなく、実行時に採取したプロファイルデータを最適化判断の材料に使う手法。分岐予測、インライン化、ループ展開、コードレイアウト(基本ブロックの配置)などの判断を、実際の実行挙動に基づいて行えるようになる。
基本的な流れ
- プロファイル収集用にコンパイルする(GCCなら
-fprofile-generate、LLVM/Clangなら-fprofile-instr-generate) - 代表的なワークロードでプログラムを実行し、プロファイルデータを採取する
- 採取したプロファイルを使って再ビルドする(
-fprofile-use/-fprofile-instr-use)
プロファイル収集方式
- 計装(instrumentation)ベース: コンパイラが分岐・呼び出し・switch-caseの各エッジにカウンタを挿入し、実行時にその頻度を記録する。GCCは終了時に
.gcdaファイルへ出力する。正確だが、計装自体にオーバーヘッドがある。 - サンプリングベース(SamplePGO/AutoFDO): perf_eventsのような既存のハードウェアパフォーマンスカウンタを使い、外部プロファイラでサンプリングする方式。計装なしで低オーバーヘッドにプロファイルを取れる。
実装例
GCC・LLVM/Clangのほか、rustc、Go(1.20以降)などの主要コンパイラ/ツールチェーンがPGOをサポートしている。
効果の実例(Go, JSON パーサーのベンチマーク)
Daniel LemireがGoのPGOをJSONパーサーで検証した記事では、3種類のJSONファイルを使ったベンチマークで以下のような結果が報告されている。
- 最良ケースで4.7%の高速化(
canada.jsonのプロファイルをcanada.json自身のベンチマークに使った場合) - 一般的には2〜3%程度の改善
- 興味深い点として、プロファイルの取り方によって「汎化」のしやすさが異なる。
twitter.jsonのプロファイルは他のファイルに対しても2.8〜3.1%の改善をもたらした一方、canada.jsonのプロファイルは自分自身にしか効かなかった
これは、PGOが「代表的な入力でプロファイルを取れば自動的に速くなる」という単純な話ではなく、プロファイルの取り方(どの入力を使うか)次第で最適化が特定のワークロードに過学習しうる、という実務上の注意点を示している。
#performance #compiler #profiling
出典
- Profile-guided optimization in Go - Daniel Lemire’s blog
- Proposal: profile-guided optimization - Go
- How To Build Clang and LLVM with Profile-Guided Optimizations - LLVM
- Profile-guided Optimization - The rustc book