Branch Probability
我们知道程序中的分支一般是有冷热之分的, 如果能够准确地预估、计算出分支的概率,那么对程序的优化是十分有益的。典型的优化如 据此调整程序的layout来降低分支预测的出错率,提高cache命中率等。
if ... else ...
for ... end for ...
switch ... case ... default ...
此外,在LLVM中,程序中代码块的frequency也主要是根据 Branch Probability 来计算的,精确的frequency对 寄存器分配(spill的抉择)等 优化也至关重要。
Branch Probability 的获取
分支概率的获取主要分静态和动态两种
静态主要是根据 关键字(hot/cold) 或者 分支的场景 来计算或预估。
declare void @trap() cold noreturn nounwind
declare void @bar() hot noreturn nounwind
while (i <= 0) {...}
动态则一般以FDO (feedback optimization)的形式来获取和使用,比如PGO (profile generate optimization),一般是先编译出能够收集分支信息的执行文件,然后执行、训练数据集,生成分支执行信息。最后依据这些生成的分支执行信息 重新编译、执行原程序。
LLVM中Branch Probability的计算
LLVM 中对分支概率的计算主要 在 BranchProbabilityInfo::calculate 函数 中完成。
lib/Analysis/BranchProbabilityInfo.cpp
1217 void BranchProbabilityInfo::calculate(const Function &F, const LoopInfo &LoopI,
1218 const TargetLibraryInfo *TLI,
1219 DominatorTree *DT,
1220 PostDominatorTree *PDT) {
// 该函数主要根据一些已知的静态信息来 粗略地 计算,传递 分支概率。
// 这些已知的静态信息主要有 'unreachable', 'noreturn', 'cold', 'unwind' blocks等。
1244 computeEestimateBlockWeight(F, DT, PDT);
// 倒序对 有条件分支的 BB进行遍历
1248 for (const auto *BB : post_order(&F.getEntryBlock())) {
// FDO的分支运行信息一般都保存在Metadata中,因此优先根据FDO的分支信息(如果有的话)来建立分支概率。
// 可以参考我的另一篇文章:《LLVM 如何利用 Profile Guided Optimization (PGO)信息》
1254 if (calcMetadataWeights(BB))
1255 continue;
下面四个可以理解为根据分支的特定场景、模型来估计分支的概率
// 该部分主要对loop场景进行预估,比如回边的概率一般要比退出边的概率高很多。
// block BB2.
// |
// V
// BB1<-+
// | |
// | | (Weight = 124)
// V |
// BB2--+
// |
// | (Weight = 4)
// V
// BB3
//
// Probability of the edge BB2->BB1 = 124 / (124 + 4) = 0.96875
// Probability of the edge BB2->BB3 = 4 / (124 + 4) = 0.03125
1256 if (calcEstimatedHeuristics(BB))
1257 continue;
// 该部分主要对指针(2个)场景进行估计,经典的比如 “if(ptr != null)”我们认为总是大概率为真的。
// “if(ptr != null)”/“if(ptr == null)” 为真的概率 37.50% 假的概率 62.50%
// 在函数实现中,这些概率主要是通过 查表 来确定的,其它函数也类似。PointerTable表:
// {ICmpInst::ICMP_NE, {PtrTakenProb, PtrUntakenProb}}, /// p != q -> Likely
// {ICmpInst::ICMP_EQ, {PtrUntakenProb, PtrTakenProb}}, /// p == q -> Unlikely
1258 if (calcPointerHeuristics(BB))
1259 continue;
// 该部分主要针对 与常数0,1,-1的比较场景来进行 概率预估。具体见 表:
// (目前 Unlikely 12/32 (37.50%); Likely:20/32 (62.50%))
// {CmpInst::ICMP_EQ, {ZeroUntakenProb, ZeroTakenProb}}, /// X == 0 -> Unlikely
// {CmpInst::ICMP_NE, {ZeroTakenProb, ZeroUntakenProb}}, /// X != 0 -> Likely
// {CmpInst::ICMP_SLT, {ZeroUntakenProb, ZeroTakenProb}}, /// X < 0 -> Unlikely
// {CmpInst::ICMP_SGT, {ZeroTakenProb, ZeroUntakenProb}}, /// X > 0 -> Likely
// {CmpInst::ICMP_SLT, {ZeroUntakenProb, ZeroTakenProb}}, /// X < 1 -> Unlikely
// {CmpInst::ICMP_EQ, {ZeroUntakenProb, ZeroTakenProb}}, /// X == -1 -> Unlikely
// {CmpInst::ICMP_NE, {ZeroTakenProb, ZeroUntakenProb}}, /// X != -1 -> Likely
// {CmpInst::ICMP_SGT, {ZeroTakenProb, ZeroUntakenProb}}, /// X >= -1 -> Likely
// 此外,该函数还对 比较对象 做了区分,比如对“X == 0”正常来说是Unlikely的,但如果X是
// strcmp等一些标准函数的返回值,则认为等于0(字符串相等)的概率大。
1260 if (calcZeroHeuristics(BB, TLI))
1261 continue;
// 该部分主要针对 浮点数的比较场景 来预估概率:
// f1 == f2 -> Unlikely (目前 Unlikely 12/32 (37.50%); Likely:20/32 (62.50%))
// f1 != f2 -> Likely
// {FCmpInst::FCMP_ORD, {FPOrdTakenProb, FPOrdUntakenProb}}, /// !isnan -> Likely
// {FCmpInst::FCMP_UNO, {FPOrdUntakenProb, FPOrdTakenProb}}, /// isnan -> Unlikely
1262 if (calcFloatingPointHeuristics(BB))
1263 continue;
1264 }
...}