LLVM/IR 级 PGO 插桩

用最大生成树减少 PGO 边计数插桩

LLVM 对控制流图按边权从高到低选生成树,用并查集排除环,把边计数器放在非树边上;这是 Kruskal 的最大生成树变体。

llvmorg-19.1.0图处理与编译核验 2026-10-12
相关概念

工程问题#

PGO 需要获得控制流边的执行次数,但每条边都增加计数器会扩大二进制并引入运行开销。基本块的流入与流出计数满足守恒关系,因此可以只测一部分边,再推导其余边的次数。

实际实现#

CFGMST 为控制流边赋权,按降序排序,每步优先取当前权重最高且不会成环的边,用带路径压缩和按秩合并的并查集检验连通性。高权边优先进入树,PGO 的 getInstrBB 跳过树边,只为可插桩的非树边安排计数器。目标是让更频繁执行的边尽量不直接计数;LLVM 还会调整关键边的权重,并优先纳入部分难以拆分的 landing pad 边。

处理流程#

  1. 构建包含入口、出口虚拟边的控制流图,并估计边的执行权重
  2. 按边权降序处理,用并查集判断两个端点是否已连通
  3. 将连接不同分量的边加入树,跳过会形成环的边
  4. 为非树边选取插桩位置,必要时拆分关键边,再利用流量守恒还原树边计数

源码片段#

固定版本 · 原文摘录
llvm/lib/Transforms/Instrumentation/PGOInstrumentation.cppllvmorg-19.1.0 · L9–L23
查看原文件
9// This file implements PGO instrumentation using a minimum spanning tree based10// on the following paper:11//   [1] Donald E. Knuth, Francis R. Stevenson. Optimal measurement of points12//   for program frequency counts. BIT Numerical Mathematics 1973, Volume 13,13//   Issue 3, pp 313-32214// The idea of the algorithm based on the fact that for each node (except for15// the entry and exit), the sum of incoming edge counts equals the sum of16// outgoing edge counts. The count of edge on spanning tree can be derived from17// those edges not on the spanning tree. Knuth proves this method instruments18// the minimum number of edges.19//20// The minimal spanning tree here is actually a maximum weight tree -- on-tree21// edges have higher frequencies (more likely to execute). The idea is to22// instrument those less frequently executed edges to reduce the runtime23// overhead of instrumented binaries.
PGOInstrumentation · 计数恢复与最大权重目标

文件开头说明流量守恒和树边计数恢复,并明确这里实际选择最大权重树,使非树边的执行频率尽量较低。

llvm/include/llvm/Transforms/Instrumentation/CFGMST.hllvmorg-19.1.0 · L59–L73
查看原文件
59  // Find the root group of the G and compress the path from G to the root.60  BBInfo *findAndCompressGroup(BBInfo *G) {61    if (G->Group != G)62      G->Group = findAndCompressGroup(static_cast<BBInfo *>(G->Group));63    return static_cast<BBInfo *>(G->Group);64  }6566  // Union BB1 and BB2 into the same group and return true.67  // Returns false if BB1 and BB2 are already in the same group.68  bool unionGroups(const BasicBlock *BB1, const BasicBlock *BB2) {69    BBInfo *BB1G = findAndCompressGroup(&getBBInfo(BB1));70    BBInfo *BB2G = findAndCompressGroup(&getBBInfo(BB2));7172    if (BB1G == BB2G)73      return false;
CFGMST::findAndCompressGroup / unionGroups

查找代表节点时压缩路径;若两个端点已有同一代表,加入该边会成环,合并返回 false。

llvm/include/llvm/Transforms/Instrumentation/CFGMST.hllvmorg-19.1.0 · L75–L85
查看原文件
75    // Make the smaller rank tree a direct child or the root of high rank tree.76    if (BB1G->Rank < BB2G->Rank)77      BB1G->Group = BB2G;78    else {79      BB2G->Group = BB1G;80      // If the ranks are the same, increment root of one tree by one.81      if (BB1G->Rank == BB2G->Rank)82        BB1G->Rank++;83    }84    return true;85  }
CFGMST::unionGroups · 按秩合并

将低秩树接到高秩树;两者同秩时增加新根的秩。这是该实现中并查集的维护规则。

llvm/include/llvm/Transforms/Instrumentation/CFGMST.hllvmorg-19.1.0 · L218–L238
查看原文件
218  // Sort CFG edges based on its weight.219  void sortEdgesByWeight() {220    llvm::stable_sort(AllEdges, [](const std::unique_ptr<Edge> &Edge1,221                                   const std::unique_ptr<Edge> &Edge2) {222      return Edge1->Weight > Edge2->Weight;223    });224  }225226  // Traverse all the edges and compute the Minimum Weight Spanning Tree227  // using union-find algorithm.228  void computeMinimumSpanningTree() {229    // First, put all the critical edge with landing-pad as the Dest to MST.230    // This works around the insufficient support of critical edges split231    // when destination BB is a landing pad.232    for (auto &Ei : AllEdges) {233      if (Ei->Removed)234        continue;235      if (Ei->IsCritical) {236        if (Ei->DestBB && Ei->DestBB->isLandingPad()) {237          if (unionGroups(Ei->SrcBB, Ei->DestBB))238            Ei->InMST = true;
CFGMST · 降序排序与特殊边预处理

比较器按 Weight 降序排列。一般贪心选择前,先尽可能把目标为 landing pad 的关键边放入树,体现真实插桩约束。

llvm/include/llvm/Transforms/Instrumentation/CFGMST.hllvmorg-19.1.0 · L243–L252
查看原文件
243    for (auto &Ei : AllEdges) {244      if (Ei->Removed)245        continue;246      // If we detect infinite loops, force247      // instrumenting the entry edge:248      if (!ExitBlockFound && Ei->SrcBB == nullptr)249        continue;250      if (unionGroups(Ei->SrcBB, Ei->DestBB))251        Ei->InMST = true;252    }
CFGMST::computeMinimumSpanningTree · 贪心选择

顺着已排序的边执行 unionGroups,合并成功才标记 InMST;无出口函数还会强制保留入口边用于插桩。

llvm/lib/Transforms/Instrumentation/PGOInstrumentation.cppllvmorg-19.1.0 · L779–L799
查看原文件
779// Given a CFG E to be instrumented, find which BB to place the instrumented780// code. The function will split the critical edge if necessary.781template <class Edge, class BBInfo>782BasicBlock *FuncPGOInstrumentation<Edge, BBInfo>::getInstrBB(Edge *E) {783  if (E->InMST || E->Removed)784    return nullptr;785786  BasicBlock *SrcBB = E->SrcBB;787  BasicBlock *DestBB = E->DestBB;788  // For a fake edge, instrument the real BB.789  if (SrcBB == nullptr)790    return DestBB;791  if (DestBB == nullptr)792    return SrcBB;793794  auto canInstrument = [](BasicBlock *BB) -> BasicBlock * {795    // There are basic blocks (such as catchswitch) cannot be instrumented.796    // If the returned first insertion point is the end of BB, skip this BB.797    if (BB->getFirstInsertionPt() == BB->end())798      return nullptr;799    return BB;
FuncPGOInstrumentation::getInstrBB

实际 PGO 消费树标记的位置:树边和已删除的边不安排计数器。后续代码再选基本块或拆分关键边。

六段原文分别给出插桩目标、并查集两项优化、降序排序、选边与实际消费位置。权重估计、关键边拆分和 profile 使用阶段的计数恢复位于完整源文件中。

收益与取舍#

工程分析

带来的收益

  • 减少直接测量所需的边计数器,保留恢复完整边计数的依据
  • 优先把较热的边留在树上,降低频繁执行路径的计数负担

付出的代价

  • 排序 E 条边需要 O(E log E) 时间;路径压缩和按秩合并使并查集操作具有近常数的摊还成本,此外还有控制流改写和计数恢复成本
  • 执行频率是权重估计,并非对每次实际运行开销的最优保证;关键边和异常处理约束还会影响选择
  • 生成和使用 profile 的编译阶段必须对应同一控制流图,图变化需要校验处理

可比较的选择

每条边都插入计数器计数直接,但计数器数量与动态执行开销更大,关键边仍可能需要拆分。

任意生成树同样可以利用流量守恒减少计数器数量,但不优先避开高频边。

版本与适用范围#

对应 LLVM llvmorg-19.1.0 的 IR 级边计数 PGO 路径,未分析 basic-block coverage 的旁路模式。文件及函数名含 MinimumSpanningTree,但源码注释明确实际追求最大权重树;并查集合并按边端点连通性判断,不是有向最短路径。异常处理边、入口插桩和无出口循环另有约束,因此不能把此实现说成任意控制流下无附加条件的标准最小生成树。

参考来源#

源码、官方文档与原始论文
01
llvm · PGO 插桩依据及非树边选择

llvm/lib/Transforms/Instrumentation/PGOInstrumentation.cpp · PGO 插桩依据及非树边选择

源码llvmorg-19.1.0
02
llvm · CFGMST · 权重排序与并查集合并

llvm/include/llvm/Transforms/Instrumentation/CFGMST.h · CFGMST · 权重排序与并查集合并

源码llvmorg-19.1.0
03
llvm · CFGMST · 最大权重选择及特殊边约束

llvm/include/llvm/Transforms/Instrumentation/CFGMST.h · CFGMST · 最大权重选择及特殊边约束

源码llvmorg-19.1.0

相关实现与比较#

3 个相关案例
scikit-learn·AgglomerativeClustering · single linkage1.6.1

scikit-learn 的单链聚类使用 Prim 派生的 MST-LINKAGE-CORE,维护每个未加入样本到当前集合的最小距离,随后用并查集生成聚类层次。

附源码片段 · 检索与计算
9 个匹配案例