把寄存器分配搬进 CFG:一次把「图着色」玩成线上火焰图的实战

举报
i-WIFI 发表于 2025/07/26 15:55:14 2025/07/26
【摘要】 背景6 月份给自家 DSL 后端换 LLVM 14,顺手把寄存器分配算法从「简单线性扫描」升级成「基于 CFG 的 Chaitin-Briggs」。结果:同一批脚本 CPU 降 12 %,寄存器溢出指令少 28 %,但调优过程连踩 4 个坑。今天把过程拆成 4 个片段——CFG 构建、活跃变量分析、图着色、溢出修复——所有数字来自 perf stat 与 llvm-mca,表是我手敲的,放心...

背景
6 月份给自家 DSL 后端换 LLVM 14,顺手把寄存器分配算法从「简单线性扫描」升级成「基于 CFG 的 Chaitin-Briggs」。结果:同一批脚本 CPU 降 12 %,寄存器溢出指令少 28 %,但调优过程连踩 4 个坑。今天把过程拆成 4 个片段——CFG 构建、活跃变量分析、图着色、溢出修复——所有数字来自 perf statllvm-mca,表是我手敲的,放心抄。


1. CFG:不是画个箭头就完事

1.1 基本块切分

一开始用 LLVM IR 自带的 MachineBasicBlock,结果碰到 8 MB 的巨型规则脚本,.ll 文件 40 万行,CFG dot 图直接 700 MB,Graphviz 当场 OOM。

切分策略 基本块数 dot 大小 生成时间
每行一条指令 45 w 700 MB 崩溃
合并顺序指令 2.8 w 18 MB 2.3 s

结论:别把每条 IR 当节点,顺序指令无脑合并,否则后面算法全白搭。

1.2 临界边分裂

Chaitin 算法要求 CFG 是「可归约图」,但我们手写的 DSL 会生成 switch 嵌套 goto,存在临界边。LLVM 的 SplitCriticalEdge 默认关,需要手动开:

PM.add(createCFGSimplificationPass());
PM.add(createCriticalEdgeSplittingPass());

不开的后果:活跃变量分析漏掉跨边的 φ-node,寄存器分配后莫名多出 6 % mov 指令。


2. 活跃变量:别把死变量当活人

2.1 数据流方程

经典 IN/OUT 方程:

IN[B] = use[B]  (OUT[B] - def[B])
OUT[B] =IN[succ]

DSL 里大量使用 64-bit mask,结果掩码变量生命周期极短,却占了一个整型寄存器。第一次跑算法时 38 % 的变量被误判为活跃。

优化 误判率 溢出指令
原始 IN/OUT 38 % 12 k
加 dead mask 裁剪 5 % 7.8 k

办法:在数据流之前跑一次「掩码死码消除」,把只参与位运算、最终结果不使用的变量直接标记 dead。


3. 图着色:贪心 + 合并才是正解

3.1 冲突图规模

一开始用 O(n²) 暴力建图,45 k 个变量直接炸内存。后面改成邻接表 + bucket 排序,内存从 3.2 GB 降到 220 MB。

建图方式 内存峰值 时间
邻接矩阵 3.2 GB 11 s
邻接表 + bucket 220 MB 1.4 s

3.2 合并启发

Chaitin-Briggs 的 coalescing 阶段默认保守,结果 DSL 里大量 mov r1, r2; use r2 被禁止合并,白白多 5 k 条指令。
coalescing-threshold 从 1 调到 3(即允许 3 度以内合并),溢出数再降 18 %。

合并阈值 溢出指令 最终 mov 数
保守(1) 7.8 k 11 k
激进(3) 6.4 k 8.5 k

4. 溢出修复:别把栈当无限仓库

4.1 溢出算法

溢出阶段默认 spill everywhere,结果 64-bit 栈槽对齐后多吃了 40 % 内存。我们改写成「按需分段溢出」:

  • 只在 def 和 use 点插 load/store
  • 连续使用区间复用同一槽位。
策略 栈槽字节 load/store 指令
全区间 520 k 6.4 k
分段 340 k 4.9 k

4.2 回滚保护

线上灰度时曾出现寄存器压力极端场景(> 95 % 变量冲突),着色失败回退到「全部栈变量」。为了兜底,在编译期插桩:

if (coloring_failed) {
    emit_trap("REG_SPILL_BACKUP");
}

上线一个月,触发了 3 次,全部落在测试流量,没炸生产。


5. 上线效果总览

指标 线性扫描 Chaitin-Briggs + CFG 降幅
CPU % 100 % 88 % 12 % ↓
溢出指令 20 k 6.4 k 68 % ↓
内存峰值 1.1 GB 1.0 GB 9 % ↓
编译时间 2.1 s 2.8 s 慢 0.7 s

6. 血泪 checklist(现场手抄)

坑点 解决姿势
CFG 图太大 合并顺序指令 + 关多余调试 dot
临界边未分裂 显式开 SplitCriticalEdge
死变量误判 先做 dead mask 消除
合并保守 coalescing-threshold=3
栈槽浪费 分段溢出 + 对齐压缩
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。