LLVM 分支权重中继数据¶
简介¶
分支权重中继数据将其被执行的可能性表示为分支权重(请参阅 LLVM 块频率术语)。中继数据作为 MD_prof
类型的 MDNode
分配给作为终结符的 指令
。第一个运算符始终是字符串为“branch_weights”的 MDString
节点。运算符的数量取决于终结符类型。
分支权重可以从分析文件中获取,也可以根据 __builtin_expect 和 __builtin_expect_with_probability 指令生成。
所有权重都表示为无符号 32 位值,其中较高的值表示被执行的可能性更大。
支持的指令¶
BranchInst
¶
中继数据仅分配给条件分支。 true 分支和 false 分支有两个额外的运算符。我们选择性地跟踪中继数据是否由 __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
¶
呼叫指令可以包含帶有一個或兩個權重的分支權重中繼資料。第二個權重是可選的,對應於展開分支。如果只設定了一個權重,則它包含呼叫的執行次數,並且僅在 SamplePGO 模式下使用,如呼叫指令中所述。如果指定了兩個權重,則第二個權重包含已執行的展開分支計數,第一個權重包含呼叫的執行次數減去已執行的展開分支計數。指定的兩個權重都用於計算 BranchProbability,就像 BranchInst 一樣,對於 SamplePGO,將使用兩個權重的總和。
!0 = !{
!"branch_weights",
[ !"expected", ]
i32 <INVOKE_NORMAL_WEIGHT>
[ , i32 <INVOKE_UNWIND_WEIGHT> ]
}
其他¶
不允許其他終止符指令包含分支權重中繼資料。
內建 expect
指令¶
__builtin_expect(long exp, long c)
指令提供分支預測資訊。返回值是 exp
的值。
它在條件語句中特別有用。目前 Clang 支援兩種條件語句
if
語句¶
exp
參數是條件。c
參數是預期的比較值。如果它等於 1(真),則條件很可能是真,否則條件很可能是假。例如
if (__builtin_expect(x > 0, 1)) {
// This block is likely to be taken.
}
switch
語句¶
exp
參數是值。c
參數是預期值。如果預期值未出現在 cases 清單中,則假設 default
案例很可能會被採用。
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(真),並且機率值 probability
設定為 0.8,則表示條件為真的機率為 80%,而為假的機率為 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
等於預期值的概率在第三個參數 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
節點分配給函數定義。第一個操作數是一個字符串,表示關聯計數器的名稱。
目前,支持一個計數器:“function_entry_count”。第二個操作數是一個 64 位計數器,表示調用該函數的次數(在基於插裝的配置文件的情況下)。在基於採樣的配置文件的情況下,此操作數是對函數被調用次數的近似值。
例如,在下面的代碼中,函數 foo() 的插裝表明它在運行時被調用了 2,590 次。
define i32 @foo() !prof !1 {
ret i32 0
}
!1 = !{!"function_entry_count", i64 2590}
如果 “function_entry_count” 有兩個以上的操作數,則後面的操作數是 ThinLTO 需要導入的函數的 GUID。這僅由基於採樣的配置文件設置。這是必需的,因為基於採樣的配置文件是在已經導入和內聯這些函數的二進制文件上收集的,並且我們需要確保 ThinLTO 後端中的 IR 與配置文件註釋相匹配。我們無法在調用站點上對其進行註釋的原因是,它在調用鏈中只能下降 1 級。對於 foo_in_a_cc()->bar_in_b_cc()->baz_in_c_cc() 的情況,我們需要在調用鏈中下降 2 級才能導入 bar_in_b_cc 和 baz_in_c_cc。