プロファイルに基づく最適化(PGO)の使用

Android ビルドシステムは、ブループリント ビルドルールを持つネイティブ Android モジュールにおいて、Clang のプロファイルに基づく最適化(PGO)の使用をサポートします。このページでは、Clang の PGO について説明し、PGO に使用するプロファイルを継続的に生成して更新する方法と、PGO とビルドシステムを統合する方法を(ユースケースとともに)示します。

注意: このドキュメントでは、Android プラットフォームでの PGO の使用について説明します。Android アプリから PGO を使用する方法については、こちらのページをご覧ください。

Clang の PGO について

Clang は、2 種類のプロファイルを使用して、プロファイルに基づく最適化を実施できます。

  • インストゥルメンテーション ベースのプロファイルは、インストゥルメント化されたターゲット プログラムから生成されます。こうしたプロファイルは詳細であり、実行時のオーバーヘッドが高くなります。
  • サンプリング ベースのプロファイルは、一般的に、ハードウェア カウンタのサンプリングにより生成されます。実行時のオーバーヘッドは低く、バイナリの変更またはインストゥルメンテーションなしで収集できます。インストゥルメンテーション ベースのプロファイルほど詳細ではありません。

すべてのプロファイルは、アプリの一般的な動作を喚起する典型的なワークロードから生成する必要があります。Clang は AST ベース(-fprofile-instr-generate)と LLVM IR ベース(-fprofile-generate))の両方をサポートしますが、Android はインストゥルメンテーション ベースの PGO に対する LLVM IR ベースのみをサポートします。

プロファイル収集用にビルドするには、次のフラグが必要です。

  • -fprofile-generate: IR ベースのインストゥルメンテーション。このオプションでは、バックエンドは重み付き最小全域木のアプローチを使用してインストゥルメンテーション ポイントの数を減らし、配置を重みの小さい辺に最適化します(リンクのステップにもこのオプションを使用します)。Clang ドライバは、自動的にプロファイリング ランタイム(libclang_rt.profile-arch-android.a)をリンカーに渡します。このライブラリには、プログラムの終了時にプロファイルをディスクに書き込むルーチンが含まれています。
  • -gline-tables-only: 最小限のデバッグ情報を生成するためのサンプリング ベースのプロファイル収集。

インストゥルメンテーション ベースのプロファイルには -fprofile-instr-use=pathname を使用し、サンプリング ベースのプロファイルには -fprofile-sample-use=pathname を使用して、PGO にプロファイルを使用できます。

注: コードの変更に伴って Clang がプロファイル データを使用できなくなると、-Wprofile-instr-out-of-date の警告が生成されます。

PGO の使用

PGO を使用する手順は次のとおりです。

  1. コンパイラとリンカーに -fprofile-generate を渡し、インストゥルメンテーションを使用してライブラリ / 実行ファイルをビルドします。
  2. インストゥルメント化されたバイナリで典型的なワークロードを実行することにより、プロファイルを収集します。
  3. llvm-profdata ユーティリティを使用してプロファイルを後処理します(詳細については、LLVM プロファイル ファイルの処理をご覧ください)。
  4. コンパイラとリンカーに -fprofile-use=<>.profdata を渡し、プロファイルを使用して PGO を適用します。

Android で PGO を使用する場合は、プロファイルをオフラインで収集し、コードとともにチェックインしてビルドを再現可能にする必要があります。プロファイルはコードが進化しても使用できますが、定期的に(または、プロファイルが最新でないことを Clang が警告するたびに)再生成する必要があります。

プロファイルの収集

Clang は、ライブラリのインストゥルメント化されたビルドでベンチマークを実行するか、またはベンチマークの実行時にハードウェア カウンタをサンプリングすることによって収集したプロファイルを使用できます。現時点では、Android はサンプリング ベースのプロファイル収集をサポートしていないため、インストゥルメント化されたビルドを使用してプロファイルを収集する必要があります。

  1. ベンチマークと、そのベンチマークによって包括的に使用されるライブラリのセットを決定します。
  2. pgo プロパティをベンチマークとライブラリに追加します(詳細については後述します)。
  3. 次のコマンドを使用して、これらのライブラリのインストゥルメント化されたコピーで Android ビルドを生成します。
    make ANDROID_PGO_INSTRUMENT=benchmark

benchmark は、ビルド中にインストゥルメント化されたライブラリのコレクションを識別するプレースホルダです。実際の典型的な入力は(場合によってはベンチマーク対象のライブラリにリンクしている別の実行ファイルも)PGO に固有のものではなく、このドキュメントの範囲外です。

  1. デバイス上のインストゥルメント化されたビルドをフラッシュまたは同期します。
  2. ベンチマークを実行してプロファイルを収集します。
  3. llvm-profdata ツール(後述します)を使用してプロファイルを後処理し、ソースツリーにチェックインできるようにします。

ビルド時にプロファイルを使用する

プロファイルを Android ツリーの toolchain/pgo-profiles にチェックインします。名前は、ライブラリの pgo プロパティの profile_file サブプロパティで指定されているものと一致する必要があります。ビルドシステムは、ライブラリのビルド時にプロファイル ファイルを自動的に Clang に渡します。ANDROID_PGO_DISABLE_PROFILE_USE 環境変数を true に設定すると、PGO を一時的に無効にしてパフォーマンスのメリットを測定できます。

プロダクト固有のプロファイル ディレクトリを追加で指定するには、BoardConfig.mkPGO_ADDITIONAL_PROFILE_DIRECTORIES make 変数に追加します。追加のパスを指定すると、それらのパスのプロファイルにより、toolchain/pgo-profiles のプロファイルがオーバーライドされます。

makedist ターゲットを使用してリリース イメージを生成する場合、ビルドシステムは、見つからなかったプロファイル ファイルの名前を $DIST_DIR/pgo_profile_file_missing.txt に書き込みます。このファイルをチェックすると、誤って削除されたプロファイル ファイル(自動的に PGO を無効にします)を確認できます。

Android.bp ファイルで PGO を有効にする

ネイティブ モジュールの Android.bp ファイルで PGO を有効にするには、単に pgo プロパティを指定します。このプロパティには、次のサブプロパティがあります。

プロパティ 説明
instrumentation インストゥルメンテーションを使用する PGO では、true に設定します。デフォルトは false です。
sampling サンプリングを使用する PGO では、true に設定します。デフォルトは false です。
benchmarks 文字列のリスト。リスト内のいずれかのベンチマークが ANDROID_PGO_INSTRUMENT ビルド オプションで指定されている場合、このモジュールはプロファイリング用にビルドされます。
profile_file PGO で使用するプロファイル ファイル(toolchain/pgo-profile に対応)。enable_profile_use プロパティが false に設定されている場合または ANDROID_PGO_NO_PROFILE_USE ビルド変数が true に設定されている場合を除き、ビルドは、このファイルを $DIST_DIR/pgo_profile_file_missing.txt に追加することで、このファイルが存在しないことを警告します。
enable_profile_use ビルド時にプロファイルを使用しない場合は、false に設定します。このプロパティをブートストラップ中に使用すると、プロファイル収集を有効にするか、または PGO を一時的に無効にできます。デフォルトは true です。
cflags インストゥルメント化されたビルドで使用する追加のフラグのリスト。

PGO を使用するモジュールの例:

cc_library {
    name: "libexample",
    srcs: [
        "src1.cpp",
        "src2.cpp",
    ],
    static: [
        "libstatic1",
        "libstatic2",
    ],
    shared: [
        "libshared1",
    ]
    pgo: {
        instrumentation: true,
        benchmarks: [
            "benchmark1",
            "benchmark2",
        ],
        profile_file: "example.profdata",
    }
}

ベンチマーク benchmark1 および benchmark2 が、libstatic1libstatic2、または libshared1 ライブラリの典型的な動作を喚起する場合、これらのライブラリの pgo プロパティにもベンチマークを含めることができます。Android.bpdefaults モジュールでは、複数のモジュールで同じビルドルールが繰り返されないようにするために、ライブラリのセットに対して共通の pgo を指定できます。

異なるプロファイル ファイルを選択するか、アーキテクチャの PGO を選択的に無効にするには、アーキテクチャごとに profile_fileenable_profile_usecflags プロパティを指定します。例(アーキテクチャ ターゲットを太字で示します):

cc_library {
    name: "libexample",
    srcs: [
          "src1.cpp",
          "src2.cpp",
    ],
    static: [
          "libstatic1",
          "libstatic2",
    ],
    shared: [
          "libshared1",
    ],
    pgo: {
         instrumentation: true,
         benchmarks: [
              "benchmark1",
              "benchmark2",
         ],
    }

    target: {
         android_arm: {
              pgo: {
                   profile_file: "example_arm.profdata",
              }
         },
         android_arm64: {
              pgo: {
                   profile_file: "example_arm64.profdata",
              }
         }
    }
}

インストゥルメンテーション ベースのプロファイリング中にプロファイリング ランタイム ライブラリへの参照を解決するには、ビルドフラグ -fprofile-generate をリンカーに渡します。PGO でインストゥルメント化された静的ライブラリ、すべての共有ライブラリ、静的ライブラリに直接依存するバイナリも、PGO 用にインストゥルメント化されている必要があります。ただし、このような共有ライブラリまたは実行ファイルで PGO プロファイルを使用する必要はなく、enable_profile_use プロパティを false に設定できます。この制限の範囲外で、PGO を任意の静的ライブラリ、共有ライブラリ、または実行ファイルに適用できます。

LLVM プロファイル ファイルの処理

インストゥルメント化されたライブラリまたは実行ファイルを実行すると、default_unique_id_0.profraw というプロファイル ファイルが /data/local/tmp に生成されます(ここで、unique_id はこのライブラリに固有の数値ハッシュです)。このファイルがすでに存在する場合、プロファイリング ランタイムは、プロファイルの作成中に新しいプロファイルを古いプロファイルと統合します。なお、アプリ デベロッパーは /data/local/tmp にアクセスできません。代わりに /storage/emulated/0/Android/data/packagename/files などを使用する必要があります。プロファイル ファイルの場所を変更するには、実行時に LLVM_PROFILE_FILE 環境変数を設定します。

次に、llvm-profdata ユーティリティで .profraw ファイルを変換(場合によっては複数の .profraw ファイルを結合)して、.profdata ファイルを作成します。

  llvm-profdata merge -output=profile.profdata <.profraw and/or .profdata files>

そうすると、profile.profdata をソースツリーにチェックインしてビルド時に使用できます。

ベンチマーク中に複数のインストゥルメント化されたバイナリ / ライブラリが読み込まれると、各ライブラリは、個別の一意の ID を持つ個別の .profraw ファイルを生成します。通常は、これらのファイルをすべて単一の .profdata ファイルに結合して、PGO ビルドに使用できます。ライブラリが別のベンチマークで使用される場合、そのライブラリは、両方のベンチマークからのプロファイルを使用して最適化する必要があります。この場合、llvm-profdatashow オプションが役立ちます。

  llvm-profdata merge -output=default_unique_id.profdata default_unique_id_0.profraw
llvm-profdata show -all-functions default_unique_id.profdata

unique_id を個別のライブラリにマッピングするには、unique_id ごとの show 出力で、ライブラリに固有の関数名を検索します。

ケーススタディ: ART 向けの PGO

このケーススタディでは、ART を参考例として提示しますが、ART 向けにプロファイリングされたライブラリの実際のセット、またはその相互依存関係を正確に記述するものではありません。

ART の dex2oat 事前コンパイラが依存する libart-compiler.so は、libart.so に依存します。ART ランタイムは、主に libart.so に実装されています。コンパイラとランタイムのベンチマークは異なります。

ベンチマーク プロファイリングされたライブラリ
dex2oat dex2oat(実行ファイル)、libart-compiler.solibart.so
art_runtime libart.so
  1. 次の pgo プロパティを dex2oatlibart-compiler.so に追加します。
        pgo: {
            instrumentation: true,
            benchmarks: ["dex2oat",],
            profile_file: "dex2oat.profdata",
        }
  2. 次の pgo プロパティを libart.so に追加します。
        pgo: {
            instrumentation: true,
            benchmarks: ["art_runtime", "dex2oat",],
            profile_file: "libart.profdata",
        }
  3. 次のコマンドを使用して、dex2oat ベンチマークと art_runtime ベンチマーク用にインストゥルメント化ビルドを作成します。
        make ANDROID_PGO_INSTRUMENT=dex2oat
        make ANDROID_PGO_INSTRUMENT=art_runtime
  4. または、次のコマンドを使用して、すべてのライブラリがインストゥルメント化された単一のインストゥルメント化ビルドを作成します。

        make ANDROID_PGO_INSTRUMENT=dex2oat,art_runtime
        (or)
        make ANDROID_PGO_INSTRUMENT=ALL

    2 番目のコマンドは、プロファイリング用にすべての PGO 対応モジュールをビルドします。

  5. dex2oatart_runtime を喚起するベンチマークを実行して、以下を取得します。
    • dex2oat からの 3 つの .profraw ファイル(dex2oat_exe.profdatadex2oat_libart-compiler.profdatadexeoat_libart.profdata)。これらは、LLVM プロファイル ファイルの処理で説明している方法によって特定されます。
    • 単一の art_runtime_libart.profdata
  6. 次のコマンドを使用して、dex2oat 実行ファイルと libart-compiler.so の共通の profdata ファイルを生成します。
    llvm-profdata merge -output=dex2oat.profdata \
        dex2oat_exe.profdata dex2oat_libart-compiler.profdata
  7. 2 つのベンチマークからのプロファイルを結合して、libart.so のプロファイルを取得します。
    llvm-profdata merge -output=libart.profdata \
        dex2oat_libart.profdata art_runtime_libart.profdata

    ベンチマークによってテストケースの件数とテストケースの実行時間が異なるため、2 つのプロファイルからの libart.so の未加工のカウント数が異なる可能性があります。この場合、重み付け結合を使用できます。

    llvm-profdata merge -output=libart.profdata \
        -weighted-input=2,dex2oat_libart.profdata \
        -weighted-input=1,art_runtime_libart.profdata

    上記のコマンドでは、dex2oat からのプロファイルに 2 倍の重みを割り当てています。実際の重み付けは、ドメインの知識または実験に基づいて決定してください。

  8. プロファイル ファイル dex2oat.profdata および libart.profdatatoolchain/pgo-profiles にチェックインして、ビルド時に使用できるようにします。