オーダー ファイル

オーダー ファイルは、最近考案されたリンカーの最適化手法です。テキスト ファイル形式で、関数を表すシンボルを含んでいます。lld などのリンカーでは、オーダー ファイルを使用して関数を特定の順序でレイアウトします。バイナリやライブラリに順序付きのシンボルがあると、ページ フォールトが減少し、プログラムのコールド スタート時にシンボルが効率的に読み込まれるため、プログラムの起動時間が短縮されます。

オーダー ファイル機能は、次の 3 つの手順でアプリに追加できます。

  1. プロファイルとマッピング ファイルを生成する
  2. プロファイルとマッピング ファイルからオーダー ファイルを作成する
  3. リリースビルド中にオーダー ファイルを使用してシンボルをレイアウトする

オーダー ファイルを生成する

オーダー ファイルの生成には、次の 3 つのステップが必要です。

  1. オーダー ファイルを書き込むアプリのインストルメント化されたバージョンをビルドする
  2. アプリを実行してプロファイルを生成する
  3. プロファイルとマッピング ファイルの後処理を行う

インストルメント化されたビルドを作成する

プロファイルは、アプリのインストルメント化されたビルドを実行することで生成されます。インストルメント化されたビルドでは、-forder-file-instrumentation をコンパイラ フラグとリンカーフラグの両方に追加し、-mllvm -orderfile-write-mapping=<filename>-mapping.txt を正確にコンパイラ フラグに追加する必要があります。インストルメンテーション フラグを設定すると、プロファイリングでオーダー ファイルのインストルメンテーションが有効になり、プロファイリングに必要な特定のライブラリが読み込まれます。一方、マッピング フラグは、バイナリまたはライブラリ内の各関数の MD5 ハッシュを示すマッピング ファイルを出力します。

また、インストルメンテーション フラグとマッピング フラグのどちらにも最適化フラグが必要なため、-O0 以外の最適化フラグを必ず渡すようにしてください。最適化フラグが渡されない場合、マッピング ファイルは生成されず、インストルメント化されたビルドによってプロファイル ファイルに誤ったハッシュが出力される可能性があります。

ndk-build

ndk-build が -O0 以外の最適化モードを使用するように、必ず APP_OPTIM=release を使用してビルドしてください。AGP を使用してビルドする場合は、リリースビルドで自動的に行われます。

LOCAL_CFLAGS += \
    -forder-file-instrumentation \
    -mllvm -orderfile-write-mapping=mapping.txt \

LOCAL_LDFLAGS += -forder-file-instrumentation

CMake

CMake で -O0 以外の最適化モードが使用されるように、必ず Debug 以外の CMAKE_BUILD_TYPE を使用してください。AGP を使用してビルドする場合は、リリースビルドで自動的に行われます。

target_compile_options(orderfiledemo PRIVATE
    -forder-file-instrumentation
    -mllvm -orderfile-write-mapping=mapping.txt
)
target_link_options(orderfiledemo PRIVATE -forder-file-instrumentation)

他のビルドシステム

-forder-file-instrumentation -O1 -mllvm -orderfile-write-mapping=mapping.txt を使用してコードをコンパイルします。

-O1 は特に必須ではありませんが、-O0 は使用しないでください。

リンク時に -mllvm -orderfile-write-mapping=mapping.txt を省略します。

これらのフラグはすべてリリースビルドに必要ないため、ビルド バリアントで制御する必要があります。サンプルのように CMakeLists.txt ですべて設定すればわかりやすくなります。

オーダー ファイル ライブラリを作成する

フラグに加えてプロファイル ファイルを設定し、インストルメント化されたバイナリが、実行中にプロファイルの書き込みを明示的にトリガーする必要があります。

  • プロファイル パスを設定するには、__llvm_profile_set_filename(PROFILE_DIR "/<filename>-%m.profraw") を呼び出します。渡された引数は <filename>-%m.profraw ですが、プロファイル ファイルは <filename>-%m.profraw.order として保存されます。PROFILE_DIR がアプリによって書き込み可能であり、ディレクトリにアクセスできることを確認します。
    • 多くの共有ライブラリがプロファイリングされているため、%m を使用すると、ライブラリの一意のモジュール署名に展開され、ライブラリごとに個別のプロファイルが返されるため便利です。その他のパターン指定子については、こちらのリンクをご覧ください。
  • __llvm_profile_initialize_file() を呼び出してプロファイル ファイルを設定します
  • __llvm_orderfile_dump() を呼び出してプロファイル ファイルに明示的に書き込みます

プロファイルがメモリに収集され、ダンプ関数によってファイルに書き込まれます。プロファイル ファイルが起動の終了時点まですべてのシンボルを保持するように、起動の最後にダンプ関数を呼び出す必要があります。

extern "C" {
extern int __llvm_profile_set_filename(const char*);
extern int __llvm_profile_initialize_file(void);
extern int __llvm_orderfile_dump(void);
}

#define PROFILE_DIR "<location-writable-from-app>"
void workload() {
  // ...
  // run workload
  // ...

  // set path and write profiles after workload execution
  __llvm_profile_set_filename(PROFILE_DIR "/default-%m.profraw");
  __llvm_profile_initialize_file();
  __llvm_orderfile_dump();
  return;
}

プロファイルのビルドを実行する

物理デバイスまたは仮想デバイスでインストルメント化されたアプリを実行し、プロファイルを生成します。adb pull を使用してプロファイル ファイルを抽出できます。

adb shell "run-as <package-name> sh -c 'cat /data/user/0/<package-name>/cache/default-%m.profraw.order' | cat > /data/local/tmp/default-%m.profraw.order"
adb pull /data/local/tmp/default-%m.profraw.order .

前述のように、書き込まれたプロファイル ファイルを含むフォルダにアクセスできることを確認します。仮想デバイスの場合は、多くのフォルダにはアクセスできないため、Google Play ストア アプリを含むエミュレータは使用しないことをおすすめします。

プロファイルとマッピング ファイルを後処理する

プロファイルを取得したら、マッピング ファイルを見つけて各プロファイルを 16 進数形式に変換する必要があります。通常、マッピング ファイルはアプリのビルドフォルダにあります。両方を用意したら、スクリプトを使用してプロファイル ファイルと正しいマッピング ファイルを取得して、オーダー ファイルを生成できます。

Linux / Mac / ChromeOS

hexdump -C default-%m.profraw.order > default-%m.prof
python3 create_orderfile.py --profile-file default-%m.prof --mapping-file <filename>-mapping.txt

Windows

certutil -f -encodeHex default-%m.profraw.order default-%m.prof
python3 create_orderfile.py --profile-file default-%m.prof --mapping-file <filename>-mapping.txt

スクリプトについて詳しくは、README をご覧ください。

オーダー ファイルを使用してアプリをビルドする

オーダー ファイルを生成したら、先ほどのフラグとオーダー ファイル関数は、生成ステップのみで使用するもののため、削除する必要があります。そのために必要なのは、-Wl,--symbol-ordering-file=<filename>.orderfile をコンパイル フラグとリンカーフラグに渡すことのみです。状況によっては、シンボルが見つからなかったり、移動できなかったりして警告が表示されることがありますが、-Wl,--no-warn-symbol-ordering を渡せば警告が表示されないようになります。

ndk-build

LOCAL_CFLAGS += \
    -Wl,--symbol-ordering-file=<filename>.orderfile \
    -Wl,--no-warn-symbol-ordering \

LOCAL_LDFLAGS += \
    -Wl,--symbol-ordering-file=<filename>.orderfile \
    -Wl,--no-warn-symbol-ordering \

CMake

target_compile_options(orderfiledemo PRIVATE
    -Wl,--symbol-ordering-file=<filename>.orderfile
    -Wl,--no-warn-symbol-ordering
)
target_link_options(orderfiledemo PRIVATE
    -Wl,--symbol-ordering-file=<filename>.orderfile
    -Wl,--no-warn-symbol-ordering
)

他のビルドシステム

-Wl,--symbol-ordering-file=<filename>.orderfile -Wl,--no-warn-symbol-ordering を使用してコードをコンパイルします。

詳しくは、オーダー ファイルの例をご確認ください。

オーダー ファイルの実装の詳細

オーダー ファイルを生成してビルドに使用する方法は多数あります。NDK は LLVM のメソッドを使用するため、実際の Java アプリまたは Kotlin アプリよりも、C または C++ 共有ライブラリの場合に最も役立ちます。Clang はすべての関数名(シンボル)を受け取り、その MD5 ハッシュを作成して、マッピング ファイルに対してこの関係を出力します。関数の初回実行時に、関数の MD5 ハッシュがプロファイル ファイル(profraw 形式)に書き込まれます。関数を次回以降実行しても MD5 ハッシュはプロファイル ファイルに書き込まれないため、重複することはありません。そのため、関数の最初の実行のみがこの順序で記録されます。プロファイル ファイルとマッピング ファイルを調べることで、各 MD5 ハッシュを取得して対応する関数に置き換え、オーダー ファイルを取得できます。

16 進数形式のプロファイル ファイルとマッピング ファイルの例は、それぞれ example.profexample-mapping.txt で確認できます。