2024 MICRO: The Last-Level Branch Predictor
核心问题
- 现代高性能 CPU,尤其是 server workload 上,分支预测器已经成为明显瓶颈
- 但不能简单把现有 TAGE 预测器无限做大,因为面积、延迟、能耗都会失控
那能不能像 cache 一样,为分支预测也做一个“层次化设计”?
由于分支预测状态不是“按 PC 直接索引”的,而是按 PC + History 索引;
因此,传统按空间局部性分页/分块的方法不适合 TAGE 这种 heavily-hashed predictor
论文提出一种新的组织方式 LLBP:
- 用“程序上下文(program context)”来重组分支预测状态
- 难预测分支需要的大量 pattern 分散到不同 context 中,每个 context 内只保留很少的 pattern
- 然后再通过 context 预测 + 预取把这些 pattern 提前搬到核内小 buffer 中
动机:真实服务器场景
作者先用真实服务器和仿真做了动机分析,结论很直接:
- 在 Intel Sapphire Rapids 服务器上,条件分支误预测会浪费 3.6%–20% 的执行周期,平均 9.2%
- 用 64KiB TAGE-SC-L 作为基线时,14 个工作负载上的 MPKI 为 0.29–6.4,平均 2.91。
- 如果把 TAGE-SC-L 的容量放到“无限”,平均可把误预测再降 36.5%
- 这部分收益里,87% 基本来自 TAGE 主体表容量增大
(真正的痛点还是 TAGE pattern table 容量不足)
- 这部分收益里,87% 基本来自 TAGE 主体表容量增大
所以论文真正研究的是:怎样用一种更“可物理实现”的方式,逼近大容量 TAGE 的收益
现有方法的局限
1. 直接增加 TAGE 容量
- TAGE 在前端关键路径上,容量一大,访问延迟和能耗都会上升
- 虽然 overriding 之类技术能缓和延迟问题,但存在极限
- 在考虑延迟后,更大的 predictor 可能让 IPC 反而下降
- 作者用 CACTI 估算出,若把 TAGE 容量增大 8 倍,访问能耗会增长到 4.58 倍
所以问题在于边际收益递减:
- 精度收益增长很慢
- 延迟/能耗代价增长很快
2. 传统层次化/分页式 TAGE: virtualized TAGE / paged TAGE
思路:把某段代码相关的 predictor metadata 聚在同一个 page 里,再像 cache 一样换入换出
(data prefetcher 中对于 Pointer-Chasing Prefetcher 的 prefetch table 也有类似的做法)
问题: TAGE 的索引是 hash(PC, history):
- hash 影响了空间局部性
- 相邻代码的分支,predictor entry 并不相邻
- 很难像 I-cache 那样高效预取一组“附近的 predictor 状态
- 每个静态分支所需 pattern 数量极度不均匀
- 大多数分支只需要很少 pattern
- 少数复杂分支需要上百甚至上千个 pattern
- 固定 page 大小:
- page 小: 装不下复杂分支
- page 大: 又浪费大量容量
LLBP 的核心研究点: Context Locality
论文的关键不是“再造一个更大的表”,而是先找到一种新的状态组织单位,使 predictor metadata 同时具备:
- 可分层搬运的粒度
- 相对紧凑的容量分布
- 足够好的局部性
这就引出了论文最重要的观察:context locality
- 一个分支如果需要很多 pattern,本质上说明它依赖很长的 history
- 很长 history 会跨越多个函数和代码区域,其中会穿过大量 无条件控制流转移,例如 jump/call/return
- 这些无条件转移序列,可以看成当前程序执行状态的一种“指纹”,也就是 program context
- 如果用这个 context 去切分 branch behavior,那么原来“一个静态分支对应海量 pattern”的问题,会变成“一个 context 只对应很少 pattern”
实验验证:
- 对最复杂的分支,useful pattern 的历史长度可达到 5–1500 条 branch,平均约 200
- 平均每两个无条件分支之间约有 3.89 个条件分支,因此历史长度 200 的 branch,大约跨越 50 多个 无条件分支,确实已经跨了很多上下文边界
- 在不做 context 切分时(W=0),前 128 个复杂分支中,50% 需要超过 298 个 pattern,95% 需要 2384 个。
- 如果 context 仅由前 2 个 无条件分支构成(W=2),50% 的 context 只需 3 个 useful pattern,95% 少于 40 个;
- 如果 W=32,95% 的 context 最多只需 9 个 pattern
说明:
pattern 数量之所以爆炸,不只是 branch 本身复杂,而是传统分支预测器不区分不同程序的上下文,只看历史 pattern
一旦按 context 拆开,pattern working set 会急剧收缩
LLBP 设计思路
LLBP 不是替代 TAGE,而是给 TAGE-SC-L 增加一个高容量的后备层
其定位类似于“last-level cache”
设计点:
- 把 predictor metadata 按 context 重新组织成 pattern set
- 大容量存储放在核外/慢层,小容量活跃集放在核内
- 当前 context 和即将到来的 context 的 pattern set 被预取到核内的小 buffer
- 预测时,LLBP 与原始 TAGE 并行工作,采用匹配到更长 history 的作为真实预测结果
硬件组成
LLBP 主要由四个硬件部件组成:
1. LLBP 大容量存储阵列
bulk storage: 保存大量 pattern set
- 每个 entry 对应一个 context 以及该 context 对应的一组 pattern
- 主要追求存储密度,不追求单周期低延迟
- 因为真正预测时访问的是前面的小 buffer,而不是每次都直达 bulk storage
每个 pattern 包含:
- prediction counter
- pattern tag
- history length 字段
和 TAGE 的区别:
- TAGE 把不同 history length 分布在不同表里
- LLBP 允许多个不同 history length 的 pattern 共存在同一个 pattern set
2. Pattern Buffer(PB)
核内的小 buffer,缓存:
- 当前 active context 的 pattern set
- 最近访问过的若干 context 的 pattern set
- 即将到来的 context 的预取结果
相当于 LLBP 的 L1
3. Rolling Context Register(RCR)
- 记录最近
W个无条件分支 PC - 通过 rolling XOR hash 生成当前 context ID(CCID)
代表当前代码区域的上下文
4. Context Directory(CD)
- 类似 cache tag array
- 负责用 context ID 查到对应的 pattern set 是否存在,以及它在 LLBP 中的位置
预测流程
对当前 branch:
- 和 TAGE 一样,先基于 branch PC 与 GHR 计算各个历史长度下的 hash;
- PB 中当前 context 的 pattern set 会并行比较 tag
- 命中后由 counter 给出 taken/not-taken
选择命中“最长 history”
TAGE 的一个核心原则是:长历史匹配优先于短历史匹配
LLBP 把不同 history length 放进同一个 set
- pattern set 内部按 history length 排序
- 匹配后的优先级仲裁,用与 TAGE 类似的 mux cascade 实现
LLBP 与基线 TAGE 仲裁
LLBP 与 TAGE 并行预测,然后比较二者命中的 history length:
如果 LLBP 命中的 history length 不短于 TAGE 的 provider 则 LLBP override TAGE ,否则沿用 TAGE 结果
PB Prefetching
不用额外预测器来预测 context,而是“用过去的 context 预取未来”: 把当前 context 的定义滞后 D 个无条件分支:
- 当前用于预测的 CCID,不包含最近的 D 个无条件分支;
- 而“prefetch CID”则用更近的历史来表示即将到来的上下文
这样做的效果是:
当某个即将生效的 context 已经“可知”时,真正进入该 context 还要再经过 D 个无条件分支,这段时间就成了预取窗口
论文实验中使用:
- context window
W = 8个无条件分支 - prefetch distance
D = 4个无条件分支 - 预取延迟建模为
6 cycles
LLBP 训练与替换
如果 LLBP override 了 TAGE,那么 TAGE 会取消本次更新,只更新 LLBP 的 provider
分配 pattern 策略: 根据置信度而非时间局部性
当 provider 预测错时,LLBP 分配一个更长 history 的新 pattern。过程大致四步:
- 若当前 context 还没被 LLBP 跟踪,则先为其创建新的 pattern set
- 在当前 pattern set 中选一个置信度最低的 pattern 作为 victim
- 填入新 tag / history length / 初始 counter
- 维护 pattern set 内按 history length 排序
Pattern Set 分 Bucket 排序
一个 set 内 16 个 pattern 任意 history length 排序: 复杂度太高
策略:把 16 个 pattern 分成 4 个 bucket,每个 bucket 只允许放一段历史长度范围内的 pattern
好处:
- 分配时只需在 bucket 内局部排序
- mux 复杂度和延迟更低
- history length 字段只需 2 位
实验
Workload and Setup
14 个 workload,包括:
- NodeApp、PHPWiki 两个 web 服务
- DaCapo、Renaissance、BenchBase 中的多种 Java workload
- Google 提供的 4 个数据中心 trace
仿真平台是 ChampSim,基线 core 为:
- 4GHz
- 6-way OoO
- 512 ROB
- 64KiB TAGE-SC-L
- 16K-entry BTB
- 32KiB L1I / 48KiB L1D / 2MiB L2 / 8MiB LLC
LLBP 的具体配置是:
- 每个 pattern:18 bits
- 每个 pattern set:16 个 pattern,共 288 bits
- LLBP 使用 16 个 history length(从基线 TAGE 的 21 个中选取)
- CD + LLBP 共可容纳约 14K pattern sets
- CD 容量 8.75KiB
- LLBP 容量 504KiB
- PB 容量 2.25KiB,缓存 64 个最近 pattern set
W: context ID 由几个无条件分支 hash 得到,设置为 8D: prefetch distance , Prefetch CID 和 CCID 之间滞后的无条件分支数,设置为 4
MPKI 降低
主结果是:
- 512KB LLBP + 64KB TAGE-SC-L
- baseline: 64KB TAGE-SC-L
- MPKI 降低 0.5%–25.9%
- 平均 8.9%。
理想无延迟版本 LLBP-0Lat 的平均提升是 9.9%
说明真实实现(带预取延迟)已经达到了理想情况的大约 90%
缺陷:
- 同等大致存储预算的“512K TAGE-SC-L”平均能降 27.3% MPKI
- 但 512K TAGE-SC-L 并不现实,因为延迟太高
IPC 提升
- 平均 speedup 0.63%
- LLBP-0Lat 为 0.71%
- 512K TAGE-SC-L 为 1.26%
- ideal bpu 的平均 speedup 为 3.6%
- ideal bpu 的收益明显低于先前工作和真实硬件上的 Top-Down 分析结果
- 作者归因于 ChampSim 的 core model 可能低估了分支预测改进的真实性能收益
工程可实现性
1. 带宽
LLBP 与 PB 之间传输的是整个 pattern set:
- 16-entry PB 时,总带宽约 12.1 bits/instruction
- 64-entry PB 时降到 9.9 bits/instruction
- 256-entry PB 时低于 1 byte/instruction。
论文选用 64-entry PB,认为这是容量与带宽的折中
和 L1I-L2 流量:64-entry PB 下,LLBP 的读带宽比 L1I miss 流量还低 41%
从互连带宽角度,LLBP 至少不是一个完全不切实际的方案
2. 延迟
CACTI 估算结果显示:
- 64KiB TAGE-SC-L:约 2 cycle
- 512KiB TAGE-SC-L:约 4 cycle
- LLBP lookup:约 4 cycle
- CD / PB:1 cycle 内
所以作者在仿真中把 LLBP 的预取延迟设为 6 cycles,用于覆盖 CD + LLBP 顺序访问和额外逻辑延迟
3 能耗
单次访问能耗上:
- 512KiB TAGE-SC-L 是 64K TAGE-SC-L 的 4.58 倍
- LLBP 本体单次访问是 4.44 倍
- 但 CD 和 PB 的单次访问只分别是 0.3 倍 和 0.25 倍
访问频率:
- 基线 TAGE 和 PB 每周期都用
- CD 只在 context 切换时用,平均约每 6.3 cycles
- LLBP 本体只在 PB miss 且存在该 context 时访问,平均约每 7.7 cycles 一次
综合访问频率: LLBP 全部结构合计能耗大约是 64K TAGE-SC-L 的 51%–57%
敏感性与有效性分析
1. prefetch distance 与 history 选择
作者比较了不同 history 类型用于 context 的效果,发现:
- 只用 unconditional branch history 最好
D = 4时达到最佳 MPKI reduction- 若把 conditional branch 也混进 context history,会引入噪声,反而更差
说明对“context”定义很重要:抓的是 跨代码区域的全局控制流骨架,而不是所有控制流细节
- 若 context 太细,会过拟合;
- 若太粗,又无法区分
2. pattern set 的大小与 context 数量
- 16K contexts,每个 8 patterns 时,就能得到约 11% 的 MPKI reduction
- 每个 context 从 8 patterns 提到 16 patterns,还能再多拿 2.6%
- 再继续加到 32 或 64,收益已经明显递减
同时,增加 context 数量基本接近线性提升,直到大约 14K sets 后才开始放缓。
作者据此认为,LLBP 的收益主要还是由总体存储容量驱动,而 context-based 组织能有效把高度偏斜的 working set 打散。
3. LLBP 实际预测覆盖率
- LLBP 只为 14.8% 的动态条件分支提供预测;
- 一旦提供预测,其中 77% 的情况下会 override 基线;
- 但坏 override 只有 6.8%;
- 不过其中 59% 的 override 实际是“冗余的”: 即使 override 了,结果也和基线一致
表明:
- LLBP 不是一个通用大而全 predictor,而是一个针对复杂分支的专门补丁层
- 很准,但存储效率不够高
Futurn Work
1. MPKI 收益明显,但提升有限
- 同等大致容量预算的 512K TAGE-SC-L 参考值是 27.3% (因延迟不可实现)
- LLBP 平均 MPKI 降低 8.9% (从“精度上限”看,LLBP 提升有限)
2. IPC 收益偏小
- 平均 speedup 只有 0.63%
- 作者解释: 用 Top-Down 和 prior work 来说明这更可能是 ChampSim 的问题,而不是设计本身无用
3. 对间接分支、pipeline reset 较敏感
针对某些 workload 例如 PHPWiki 上,由于 indirect call misprediction 多,pipeline flush 会重置 LLBP 的预取过程,导致收益明显不如理想情况
说明 LLBP 对“前端频繁清空”的场景有脆弱性,其有效性依赖于预取窗口能稳定存在
4. context hash 是近似,不是精确调用链
- RCR 使用的是无条件分支 PC 的 rolling XOR hash,并不是精确的 call chain
- 成本低,但一定会引入 collision 和信息丢失
5. 存储效率低
- LLBP 只覆盖 14.8% 的动态条件分支
- 59% 的 override 其实和基线相同
说明有不少存储和带宽花在“正确但不必要的重复确认”上
这篇论文的后续工作:2026 HPCA: The Last-Level Branch Predictor Revisited