LLVM 分支权重元数据¶
简介¶
分支权重元数据表示分支被采纳的可能性(参见 LLVM 块频率术语)。元数据作为 MD_prof
类型的 MDNode
被分配给作为终止符的 Instruction
。第一个操作数始终是带有字符串 “branch_weights” 的 MDString
节点。操作数的数量取决于终止符类型。
分支权重可能从性能分析文件中获取,或者基于 __builtin_expect 和 __builtin_expect_with_probability 指令生成。
所有权重都表示为无符号 32 位值,其中较高的值表示被采纳的更大可能性。
支持的指令¶
BranchInst
¶
元数据仅分配给条件分支。真分支和假分支有两个额外的操作数。我们可选择性地跟踪元数据是否由 __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 指令可能具有带有一个或两个权重的分支权重元数据。第二个权重是可选的,对应于 unwind 分支。如果仅设置一个权重,则它包含调用的执行计数,并且仅在 SamplePGO 模式下使用,如调用指令所述。如果指定了两个权重,则第二个权重包含 unwind 分支被采纳的计数,第一个权重包含调用的执行计数减去 unwind 分支被采纳的计数。指定的两个权重都用于计算 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
参数是预期值。如果预期值未在 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
语句¶
如果 expect 比较值 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 位计数器,指示此函数被调用的次数(在基于 instrumentation 的配置文件的情况下)。在基于采样的配置文件的情况下,此操作数是函数被调用次数的近似值。
例如,在下面的代码中,函数 foo() 的 instrumentation 指示它在运行时被调用了 2,590 次。
define i32 @foo() !prof !1 {
ret i32 0
}
!1 = !{!"function_entry_count", i64 2590}
如果 “function_entry_count” 有超过 2 个操作数,则后面的操作数是 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。