Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

プロファイル誘導最適化

rustc はプロファイル誘導最適化(PGO)をサポートしています。 この章では、PGO とは何か、そしてそのサポートが rustc でどのように 実装されているかを説明します。

プロファイル誘導最適化とは?

PGO の基本的な概念は、プログラムの典型的な実行に関するデータ (例: どの分岐を通る可能性が高いか)を収集し、そのデータを使用して インライン化、マシンコードの配置、レジスタ割り当てなどの最適化に 情報を与えることです。

プログラムの実行に関するデータを収集する方法はいくつかあります。 1 つは、プログラムをプロファイラー(perf など)の内部で実行する方法で、 もう 1 つは、インストルメント化されたバイナリ、つまりデータ収集機能が 組み込まれたバイナリを作成し、それを実行する方法です。 後者の方が通常、より正確なデータを提供します。

PGO は rustc でどのように実装されていますか?

rustc の現在の PGO 実装は完全に LLVM に依存しています。 LLVM は実際には PGO の複数の形式をサポートしています。

  • perf のような外部プロファイリングツールを使用してプログラムの実行に関する データを収集する、サンプリングベースの PGO。
  • コードカバレッジ基盤を使用してプロファイリング情報を収集する、 GCOV ベースのプロファイリング。
  • コンパイラーフロントエンド(例: Clang)が、生成する LLVM IR に インストルメンテーション intrinsic を挿入する、フロントエンドベースの インストルメンテーション(ただし、1「注記」を参照)。
  • LLVM が最適化パス中にインストルメンテーション intrinsic を自分で挿入する、 IR レベルのインストルメンテーション。

rustc がサポートしているのは最後のアプローチである IR レベルのインストルメンテーションのみです。主な理由は、 それがほぼ完全に LLVM で実装されており、Rust 側でほとんど メンテナンスを必要としないためです。幸いなことに、これは最も現代的なアプローチでもあり、 最良の結果をもたらします。

つまり、ここで扱っているのはインストルメンテーションベースのアプローチです。すなわち、プロファイリングデータは、 最適化対象のプログラムを特別にインストルメント化したバージョンによって生成されます。 インストルメンテーションベースの PGO には、コンパイル時コンポーネントと 実行時コンポーネントの 2 つのコンポーネントがあり、それらがどのように相互作用するかを理解するには、 全体的なワークフローを理解する必要があります。

全体的なワークフロー

PGO で最適化されたプログラムの生成には、次の 4 つのステップが含まれます。

  1. インストルメンテーションを有効にしてプログラムをコンパイルする(例: rustc -C profile-generate main.rs
  2. インストルメント化されたプログラムを実行する(例: ./main)。これにより default-<id>.profraw ファイルが生成される
  3. LLVM の llvm-profdata ツールを使用して、.profraw ファイルを .profdata ファイルに変換する。
  4. 今度はプロファイリングデータを使用して、プログラムを再度コンパイルする (例: rustc -C profile-use=merged.profdata main.rs

コンパイル時の側面

上記のワークフローのどのステップにいるかに応じて、コンパイル時には 2 つの異なることが 起こり得ます。

インストルメンテーション付きのバイナリを作成する

前述のとおり、プロファイリングインストルメンテーションは LLVM によって追加されます。 rustc は、LLVM PassManager の作成時に適切なフラグを設定することで、 LLVM にそうするよう指示します。

	// `PMBR` は `LLVMPassManagerBuilderRef` です
    unwrap(PMBR)->EnablePGOInstrGen = true;
    // インストルメント化されたバイナリには、`.profraw` ファイルのデフォルト出力パスが
    // ハードコードされています:
    unwrap(PMBR)->PGOInstrGen = PGOGenPath;

rustc はまた、LLVM のプロファイリングランタイムからの一部のシンボルが 適切なエクスポートレベルでマークすることによって削除されないようにする必要があります。

最適化がプロファイリングデータを使用するバイナリをコンパイルする

上記のワークフローの最後のステップでは、プログラムが再度コンパイルされ、 コンパイラーは収集されたプロファイリングデータを使用して最適化の判断を行います。 ここでも rustc は作業の大半を LLVM に任せており、基本的には LLVM PassManagerBuilder に プロファイリングデータがどこにあるかを伝えるだけです。

	unwrap(PMBR)->PGOInstrUse = PGOUsePath;

残りは LLVM が行います(例: 分岐の重みを設定する、関数に coldinlinehint をマークする、など)。

実行時の側面

インストルメンテーションベースのアプローチには常に実行時コンポーネントもあります。つまり、 インストルメント化されたプログラムができたら、そのプログラムを実行して プロファイリングデータを生成する必要があり、このプロファイリングデータを収集して永続化するには、 何らかの基盤が必要です。

LLVM の場合、これらの実行時コンポーネントは compiler-rt に実装され、インストルメント化された バイナリに静的リンクされます。 この rustc バージョンは library/profiler_builtins にあり、 基本的には compiler-rt の C コードを Rust クレートにまとめたものです。

profiler_builtins をビルドするには、rustcbootstrap.tomlprofiler = true を設定する必要があります。

PGO のテスト

PGO ワークフローは複数のコンパイラー呼び出しにまたがるため、テストの大半は run-make tests で行われます(関連するテストは名前に pgo を含みます)。 また、期待されるインストルメンテーションアーティファクトが LLVM IR に現れることを確認する codegen test もあります。

追加情報

Clang のドキュメントには、LLVM における PGO に関する優れた概要が含まれています。


  1. 注記: rustc は現在、実験的なオプション -C instrument-coverage によって、フロントエンドベースのカバレッジ インストルメンテーションをサポートしていますが、これらのカバレッジ結果を PGO に使用することは、 現時点では試みられていません。