計測プロファイル形式

概要

Clangは、計測による2種類のプロファイリング[1]をサポートします。フロントエンドベースとIRベースであり、どちらも様々なユースケース[2]に対応できます。このドキュメントでは、IRPGOユースケースに重点を置いて、計測されたプロファイルを保存するための2つのバイナリシリアライゼーション形式(rawとindexed)について説明します。特定のヘッダーフィールドとペイロードセクションがユースケース間で異なる解釈方法を持つ場合、ドキュメントはIRPGOに基づいています。

注記

フロントエンドで生成されたプロファイルは、ソースベースのコードカバレッジのためにカバレッジマッピングと共に使用されます。カバレッジマッピング形式はプロファイル形式とは異なります。

Rawプロファイル形式

rawプロファイルは、計測されたバイナリを実行することで生成されます。実行ファイルまたは共有ライブラリ[3]からのrawプロファイルデータは、ヘッダーと複数のセクションで構成され、各セクションはメモリダンプとして扱われます。rawプロファイルデータは、コンパクトで生成速度が速い必要があります。

rawプロファイル形式には、後方互換性または前方互換性の保証はありません。つまり、コンパイラとツール、プロファイルを解析するために特定のrawプロファイルバージョンを必要とします。

最適化されたビルドのためにプロファイルをコンパイラにフィードバックする(例:IR計測の場合-fprofile-use)には、rawプロファイルをインデックス形式に変換する必要があります。

一般的なストレージレイアウト

rawプロファイルデータ形式のストレージレイアウトを以下に示します。基本的に、rawプロファイルがメモリバッファに読み込まれる場合、セクションの実際のバイトオフセットは、レイアウトにおけるセクションの順序と、その前のすべてのセクションのサイズ情報から推測されます。

+----+-----------------------+
|    |        Magic          |
|    +-----------------------+
|    |        Version        |
|    +-----------------------+
H    |   Size Info for       |
E    |      Section 1        |
A    +-----------------------+
D    |   Size Info for       |
E    |      Section 2        |
R    +-----------------------+
|    |          ...          |
|    +-----------------------+
|    |   Size Info for       |
|    |      Section N        |
+----+-----------------------+
P    |       Section 1       |
A    +-----------------------+
Y    |       Section 2       |
L    +-----------------------+
O    |          ...          |
A    +-----------------------+
D    |       Section N       |
+----+-----------------------+

注記

特定の配置要件を満たすために、セクションにパディングが追加される場合があります。簡略化のために、パディングのみを目的としたヘッダーフィールドとデータセクションは、上のデータレイアウトグラフとこのドキュメントの残りの部分では省略されています。

ペイロードセクション

バイナリID

ソースコードカバレッジのためにバイナリとプロファイルを関連付けるために、計測されたバイナリのバイナリIDを格納します。バイナリIDRFCを参照してください。

プロファイルメタデータ

このセクションは、カウンタと値プロファイルを計測されたコード領域(例:IRPGOの場合LLVM IR)にマッピングするためのメタデータを格納します。

メタデータのメモリ内表現は__llvm_profile_dataです。いくつかのフィールドは、プロファイル内の他のセクションからのデータを参照するために使用されます。フィールドは次のように文書化されています。

NameRef

関数のPGO名のMD5。PGO名は[<filepath><delimiter>]<mangled-name>形式で、<filepath><delimiter>は、ローカルリンケージ関数に、可能性のある同一関数を区別するために提供されます。

FuncHash

制御フローグラフと計測された値サイトを考慮した関数のIRのチェックサム。computeCFGHashを参照してください。

CounterPtr

プロファイルデータと対応するカウンタの開始位置間のメモリ内アドレスの差。カウンタの位置はこのように(リンク時定数として)格納されることで、シンボルのアドレスを直接スナップショットするよりも、計測されたバイナリのサイズを削減できます。コミットa1532edを参照してください。

注記

CounterPtrは、IRPGO以外のユースケースでは異なる値を表す場合があります。例えば、バイナリプロファイルの相関付けでは、カウンタの絶対アドレスを表します。不明な場合は、ソースコードを確認してください。

BitmapPtr

プロファイルデータと対応するビットマップの開始アドレス間のメモリ上のアドレス差。

注記

CounterPtrと同様に、このフィールドはIRPGO以外のユースケースでは異なる値を表す場合があります。

FunctionPointer

インストルメント化されたバイナリの実行時に関数のアドレスを記録します。これは、生のプロファイルからインデックス付きのプロファイルへの変換時に、間接呼び出しのプロファイルされた呼び出し先アドレスをNameRefにマッピングするために使用されます。

Values

2次元配列で値プロファイルを表現します。最初の次元の要素数は、あらゆる種類のインストルメント化された値サイトの数です。最初の次元の各要素は連結リストの先頭であり、2番目の次元の各要素は連結リストの要素で、<profiled-value, count>をペイロードとして持ちます。これは、値プロファイルの書き出し時にコンパイラのランタイムによって使用されます。

注記

値プロファイリングは、フロントエンドとIR PGOインストルメンテーションでサポートされていますが、すべての場合でサポートされているわけではありません(例:軽量インストルメンテーション)。

NumCounters

インストルメント化された関数のカウンタの数。

NumValueSites

これはカウンタの配列であり、各カウンタは関数内の特定の種類の値のインストルメント化されたサイト数を表します。

NumBitmapBytes

関数のビットマップバイト数。

プロファイルカウンタ

PGO[4]では、特定のFuncHashを持つインストルメント化された関数の内部のカウンタは、連続して、インストルメンテーションポイントの選択と一致する順序で格納されます。

上記のように、記録されたカウンタのオフセットはプロファイルメタデータに対する相対値です。では、生のプロファイルデータ内で関数のカウンタはどのように配置されるのでしょうか?

基本的に、プロファイルリーダーはプロファイルメタデータ(プロファイルメタデータセクションから)を繰り返し処理し、以下に示すように記録された相対距離を利用します。

       + --> start(__llvm_prf_data) --> +---------------------+ ------------+
       |                                |       Data 1        |             |
       |                                +---------------------+  =====||    |
       |                                |       Data 2        |       ||    |
       |                                +---------------------+       ||    |
       |                                |        ...          |       ||    |
Counter|                                +---------------------+       ||    |
 Delta |                                |       Data N        |       ||    |
       |                                +---------------------+       ||    |   CounterPtr1
       |                                                              ||    |
       |                                              CounterPtr2     ||    |
       |                                                              ||    |
       |                                                              ||    |
       + --> start(__llvm_prf_cnts) --> +---------------------+       ||    |
                                        |        ...          |       ||    |
                                        +---------------------+  -----||----+
                                        |    Counter for      |       ||
                                        |       Data 1        |       ||
                                        +---------------------+       ||
                                        |        ...          |       ||
                                        +---------------------+  =====||
                                        |    Counter for      |
                                        |       Data 2        |
                                        +---------------------+
                                        |        ...          |
                                        +---------------------+
                                        |    Counter for      |
                                        |       Data N        |
                                        +---------------------+

グラフでは、

  • プロファイルヘッダーは、値をstart(__llvm_prf_cnts) - start(__llvm_prf_data)とするCounterDeltaを記録します。便宜上、以下ではこれをCounterDeltaInitValと呼びます。

  • 各プロファイルデータレコードProfileDataNについて、CounterPtrstart(CounterN) - start(ProfileDataN)として記録されます。ここで、ProfileDataN__llvm_prf_dataのN番目のエントリであり、CounterNは対応するプロファイルカウンタを表します。

リーダーが次のデータレコードに進むたびに、更新CounterDeltaは、1つのProfileDataのサイズを引きます。

最初のデータレコードに対応するカウンタについて、カウンタセクションの先頭からのバイトオフセットはCounterPtr1 - CounterDeltaInitValとして計算されます。プロファイルリーダーが2番目のデータレコードに進むと、CounterDeltaCounterDeltaInitVal - sizeof(ProfileData)に更新されます。したがって、カウンタセクションの先頭からの相対バイトオフセットはCounterPtr2 - (CounterDeltaInitVal - sizeof(ProfileData))として計算されます。

ビットマップ

このセクションは、ソースベースの修正条件/決定カバレッジコードカバレッジに使用されます。ビットマップRFCで設計を確認してください。

名前

このセクションには、関数のPGO名の圧縮された連結文字列が含まれている可能性があります。圧縮されている場合は、zlibライブラリが使用されます。

関数名は、生のプロファイルがインデックス付きのプロファイルに変換されるときに、PGOデータハッシュテーブルのキーとして機能します。また、llvm-profdataがプロファイルを人間が読み取れる形式で表示するためにも不可欠です。

仮想テーブルプロファイルデータ

このセクションは、型プロファイリングに使用されます。各エントリは1つの仮想テーブルに対応し、次のC++構造体で定義されます。

struct VTableProfData {
  // The start address of the vtable, collected at runtime.
  uint64_t StartAddress;
  // The byte size of the vtable. `StartAddress` and `ByteSize` specifies an address range to look up.
  uint32_t ByteSize;
  // The hash of vtable's (PGO) name
  uint64_t MD5HashOfName;
};

プロファイル使用時、コンパイラはソートされた仮想テーブルアドレス範囲でプロファイルされたアドレスを検索し、ハッシュされた名前を使用してアドレスを特定の仮想テーブルにマッピングします。

仮想テーブル名

このセクションは、上記の関数名セクションに似ていますが、プロファイルされた仮想テーブルのPGO名が含まれています。これはスタンドアロンのセクションであるため、生のプロファイルリーダーは対応するプロファイルデータセクションにアクセスすることで、各名前セットを直接見つけることができます。

このセクションは生のプロファイルに格納されているため、`llvm-profdata`はプロファイルを人間が読み取れる形式で表示できます。

値プロファイルデータ

このセクションには、値プロファイリングのプロファイルデータが含まれています。

プロファイルメタデータに対応する値プロファイルは、1つのレコードとして連続してシリアル化され、値プロファイルレコードはそれぞれのプロファイルデータと同じ順序で格納されるため、生のプロファイルリーダーはポインタをプロファイルデータと値プロファイルレコードの両方に同時に[5]進めることで、関数ごと、FuncHashプロファイルデータごとの値プロファイルを見つけることができます。

インデックス付きプロファイル形式

インデックス付きのプロファイルはllvm-profdataから生成されます。インデックス付きのプロファイルでは、関数データはオンディスクハッシュテーブルとして整理されているため、コンパイラはIRモジュールの関数のプロファイルデータを探すことができます。

コンパイラとツールは、インデックス付きのプロファイルとの下位互換性を維持する必要があります。つまり、新しいバージョンのコードでビルドされたツールまたはコンパイラは、古いツールまたはコンパイラによって生成されたプロファイルの理解が必要です。

一般的なストレージレイアウト

ASCIIアートは、インデックス付きのプロファイルの一般的なストレージレイアウトを示しています。具体的には、インデックス付きのプロファイルヘッダーは、個々のペイロードセクションのバイトオフセットを記述しています。

                +-----------------------+---+
                |        Magic          |   |
                +-----------------------+   |
                |        Version        |   |
                +-----------------------+   |
                |        HashType       |   H
                +-----------------------+   E
                |       Byte Offset     |   A
        +------ |      of section A     |   D
        |       +-----------------------+   E
        |       |       Byte Of fset    |   R
    +-----------|      of section B     |   |
    |   |       +-----------------------+   |
    |   |       |         ...           |   |
    |   |       +-----------------------+   |
    |   |       |      Byte Offset      |   |
+---------------|     of section Z      |   |
|   |   |       +-----------------------+---+
|   |   |       |    Profile Summary    |   |
|   |   |       +-----------------------+   P
|   |   +------>|      Section A        |   A
|   |           +-----------------------+   Y
|   +---------->|      Section B        |   L
|               +-----------------------+   O
|               |         ...           |   A
|               +-----------------------+   D
+-------------->|      Section Z        |   |
                +-----------------------+---+

注記

プロファイルサマリーセクションは、ペイロードの先頭に配置されます。ヘッダーの直後にあるため、ヘッダーを読み込んだ後、その位置は暗黙的にわかります。

ヘッダー

ヘッダー構造体は真実の源であり、構造体のフィールドはヘッダーの内容を説明する必要があります。高レベルでは、`*Offset`フィールドはセクションのバイトオフセットを記録し、リーダーはそれを使用して興味のあるセクションを見つけ、興味のないセクションをスキップします。

注記

インデックス付きのプロファイルの下位互換性を維持するために、既存のフィールドは構造体の定義から削除してはならず、フィールドの順序を変更してはなりません。新しいフィールドは追加する必要があります。

ペイロードセクション

(CS) プロファイルサマリー

このセクションはプロファイルヘッダーの直後です。シリアル化されたプロファイルサマリーを格納します。コンテキストに依存したIRベースのインストルメンテーションPGOの場合、このセクションには、コンテキストに依存したプロファイルに対応する追加のプロファイルサマリーが格納されます。

関数データ

このセクションは、関数とそのプロファイリングデータをオンディスクハッシュテーブルとして格納します。同じ名前の関数のプロファイルデータはグループ化され、1つのハッシュテーブルエントリを共有します(関数は異なる共有ライブラリから来る可能性があります)。それらのプロファイルデータは、キーがFuncHashであり、値が関数のプロファイル情報(InstrProfRecordで表される)であるキーバリューペアのシーケンスとして整理されます。

MemProfプロファイルデータ

このセクションは、関数のメモリプロファイリングデータを格納します。MemProfバイナリシリアル化形式RFCで設計を確認してください。

バイナリID

このセクションは、生のプロファイルからバイナリID情報を保持するために使用されます。

時間的プロファイルトレース

このセクションは、生のプロファイルから時間的プロファイル情報を保持するために使用されます。時間的プロファイリングで設計を確認してください。

仮想テーブル名

このセクションは、生のプロファイルの仮想テーブルの名前をインデックス付きのプロファイルに格納するために使用されます。

関数データハッシュテーブルのキーとして格納される関数名とは異なり、仮想テーブル名はインデックス付きのプロファイルのスタンドアロンセクションに格納する必要があります。これにより、`llvm-profdata`はプロファイルされた仮想テーブル情報を人間が読み取れる形式で表示できます。

プロファイルデータの使用方法

llvm-profdataは、インストルメンテーションベースのプロファイルデータを表示および処理するためのコマンドラインツールです。サポートされている使用方法については、llvm-profdataのドキュメントを確認してください。