LLVM ブランチウェイトメタデータ¶
はじめに¶
ブランチウェイトメタデータは、ブランチが実行される可能性をウェイトとして表します(LLVM ブロック頻度用語を参照)。メタデータは、ターミネータであるInstruction
にMD_prof
種類のMDNode
として割り当てられます。最初のオペランドは常に、文字列「branch_weights」を持つMDString
ノードです。オペランドの数はターミネータの種類によって異なります。
ブランチウェイトは、プロファイリングファイルからフェッチされるか、__builtin_expectおよび__builtin_expect_with_probability命令に基づいて生成される場合があります。
すべてのウェイトは符号なし32ビット値として表され、値が大きいほど実行される可能性が高いことを示します。
サポートされている命令¶
BranchInst
¶
メタデータは、条件付きブランチにのみ割り当てられます。trueブランチとfalseブランチには、2つの追加のオペランドがあります。オプションで、メタデータが__builtin_expect
または__builtin_expect_with_probability
によって追加されたかどうかを、オプションのフィールド!"expected"
で追跡します。
!0 = !{
!"branch_weights",
[ !"expected", ]
i32 <TRUE_BRANCH_WEIGHT>,
i32 <FALSE_BRANCH_WEIGHT>
}
SwitchInst
¶
ブランチウェイトは、すべてのcase(常にcase#0であるdefault
caseを含む)に割り当てられます。
!0 = !{
!"branch_weights",
[ !"expected", ]
i32 <DEFAULT_BRANCH_WEIGHT>
[ , i32 <CASE_BRANCH_WEIGHT> ... ]
}
IndirectBrInst
¶
ブランチウェイトは、すべての宛先に割り当てられます。
!0 = !{
!"branch_weights",
[ !"expected", ]
i32 <LABEL_BRANCH_WEIGHT>
[ , i32 <LABEL_BRANCH_WEIGHT> ... ]
}
CallInst
¶
呼び出しには、呼び出しの実行回数を含むブランチウェイトメタデータが含まれる場合があります。現在、これはSamplePGOモードでのみ使用され、サンプリングでは正確ではない可能性があるブロック数とエントリ数を増強します。
!0 = !{
!"branch_weights",
[ !"expected", ]
i32 <CALL_BRANCH_WEIGHT>
}
InvokeInst
¶
Invoke命令には、1つまたは2つのウェイトを持つブランチウェイトメタデータを含めることができます。2番目のウェイトはオプションで、アンワインドブランチに対応します。ウェイトが1つだけ設定されている場合、そこには呼び出しの実行回数が含まれており、呼び出し命令の説明にあるように、SamplePGOモードでのみ使用されます。両方のウェイトが指定されている場合、2番目のウェイトにはアンワインドブランチの実行回数が含まれており、最初のウェイトには呼び出しの実行回数からアンワインドブランチの実行回数を引いたものが含まれます。指定された両方のウェイトは、BranchInstの場合と同様にBranchProbabilityを計算するために使用され、SamplePGOでは両方のウェイトの合計が使用されます。
!0 = !{
!"branch_weights",
[ !"expected", ]
i32 <INVOKE_NORMAL_WEIGHT>
[ , i32 <INVOKE_UNWIND_WEIGHT> ]
}
その他¶
その他のターミネータ命令には、ブランチウェイトメタデータを含めることはできません。
組み込みexpect
命令¶
__builtin_expect(long exp, long c)
命令は、ブランチ予測情報を提供します。戻り値はexp
の値です。
これは、条件付きステートメントで特に役立ちます。現在、Clangは2つの条件付きステートメントをサポートしています
if
ステートメント¶
exp
パラメータは条件です。c
パラメータは、予想される比較値です。これが1(true)と等しい場合、条件はtrueになる可能性が高く、それ以外の場合は条件がfalseになる可能性が高くなります。たとえば
if (__builtin_expect(x > 0, 1)) {
// This block is likely to be taken.
}
switch
ステートメント¶
exp
パラメータは値です。c
パラメータは、予想される値です。予想される値がcaseリストに表示されない場合、default
caseが実行される可能性が高いと想定されます。
switch (__builtin_expect(x, 5)) {
default: break;
case 0: // ...
case 3: // ...
case 5: // This case is likely to be taken.
}
組み込みexpect.with.probability
命令¶
__builtin_expect_with_probability(long exp, long c, double probability)
は、__builtin_expect
と同じセマンティクスを持ちますが、呼び出し元はexp == c
である確率を提供します。最後の引数probability
は、定数浮動小数点式である必要があり、[0.0、1.0]の範囲内である必要があります。使用法も__builtin_expect
と同様です。たとえば
if
ステートメント¶
期待比較値c
が1(true)と等しく、確率値probability
が0.8に設定されている場合、条件がtrueになる確率は80%であり、falseになる確率は20%であることを意味します。
if (__builtin_expect_with_probability(x > 0, 1, 0.8)) {
// This block is likely to be taken with probability 80%.
}
switch
ステートメント¶
これは、基本的には__builtin_expect
のswitch
ステートメントと同じです。exp
が期待値と等しくなる確率は、3番目の引数probability
で与えられますが、他の値の確率は残りの確率(1.0 - probability
)の平均です。例えば
switch (__builtin_expect_with_probability(x, 5, 0.7)) {
default: break; // Take this case with probability 10%
case 0: break; // Take this case with probability 10%
case 3: break; // Take this case with probability 10%
case 5: break; // This case is likely to be taken with probability 70%
}
CFGの変更¶
ブランチウェイトメタデータは、CFGの変更に対して安全ではありません。ターミネータオペランドが変更された場合は、何らかのアクションを実行する必要があります。それ以外の場合、誤ったブランチ予測情報により、最適化の誤りが発生する可能性があります。
関数エントリ数¶
インタープロシージャル分析と最適化中に異なる関数を比較できるようにするために、MD_prof
ノードを関数定義に割り当てることもできます。最初のオペランドは、関連付けられたカウンタの名前を示す文字列です。
現在、1つのカウンタがサポートされています: "function_entry_count"。2番目のオペランドは、この関数が呼び出された回数を示す64ビットカウンタです(インストルメンテーションベースのプロファイルの場合)。サンプリングベースのプロファイルの場合、このオペランドは関数が呼び出された回数の近似値です。
たとえば、以下のコードでは、関数foo()のインストルメンテーションは、実行時に2,590回呼び出されたことを示しています。
define i32 @foo() !prof !1 {
ret i32 0
}
!1 = !{!"function_entry_count", i64 2590}
「function_entry_count」に3つ以上のオペランドがある場合、後のオペランドはThinLTOによってインポートする必要がある関数のGUIDです。これは、サンプリングベースのプロファイルによってのみ設定されます。サンプリングベースのプロファイルは、すでにこれらの関数をインポートしてインライン化していたバイナリで収集されたため、プロファイルアノテーションのためにIRがThinLTOバックエンドで一致することを確認する必要があるためです。これをコールサイトにアノテーションできない理由は、コールチェーンで1レベルしか下がれないためです。foo_in_a_cc()->bar_in_b_cc()->baz_in_c_cc()の場合、コールチェーンで2レベル下がり、bar_in_b_ccとbaz_in_c_ccの両方をインポートする必要があります。