Lec 22 复杂流水线与分支预测
Lec 16 用“预测不跳转 + 猜错就作废”应付控制冒险,这对 5 级流水线够用。但流水线越深、每周期发射的指令越多,分支猜错的代价就越大。本讲先看为什么需要更聪明的分支预测,再系统介绍静态/动态预测方案。
Outline
- 控制冒险回顾与分支代价
- 静态预测:预测不跳转
- 把分支决策提前
- 动态分支预测:1 位 / 2 位预测器
- 分支目标缓冲(BTB)
- 关联预测器与锦标赛预测器
- 复杂流水线:更深 / 多发射
控制冒险与分支代价
为维持流水线满载,每个周期都要取一条指令;但“是否跳转、跳到哪”要等分支指令走到较后的阶段才知道。在这段延迟里取入的指令若猜错就得作废(flush)——这就是控制/分支冒险。
分支代价(branch penalty)= 猜错时被作废的指令数。它随流水线深度、发射宽度增大而增大:
- 深流水线:分支决策更靠后 → 错误路径上指令更多;
- 多发射(每周期发射多条):每个错误周期丢的是“多条”指令。
针对控制冒险没有像“旁路对数据冒险”那样彻底的解法,所以用较简单的方案 + 预测。
静态预测:预测不跳转(predict not-taken)
最简单的优化:假定分支不跳转,沿顺序流继续取指、译码、执行。
- 若最终确实不跳:零开销,赚到。
- 若最终跳了:把已经在 IF/ID/EX 里的错误指令作废(把它们的控制信号清零,变成 nop),从分支目标重新取指。
若分支约一半时间不跳、且作废代价低,这就把控制冒险代价减半。
把分支决策提前
减小 taken 分支代价的办法是把分支决策移到更早的阶段(如从 MEM/EX 移到 ID)。需要两件事提前:
- 算分支目标地址:容易——PC 和立即数在 IF/ID 寄存器里都有,把分支加法器挪到 ID 即可(对所有指令都算,只在需要时用)。
- 判断分支是否成立:较难——
beq要在 ID 比较两个寄存器是否相等(XOR 各位再 OR)。这带来两个麻烦:- 需要新的旁路把还在流水线里的结果转发到 ID 的比较单元;
- 仍可能要停顿:若紧邻分支的前一条 ALU 指令产生比较所需操作数,需停 1 拍;若是 load 紧跟依赖它的分支,需停 2 拍。
把分支决策移到 ID 后,taken 分支的代价降到仅 1 条指令(正在取的那条)。在 IF 加一条 IF.Flush 控制线把 IF/ID 寄存器的指令字段清零(变 nop)即可作废。
动态分支预测
“预测不跳转”是最简单的静态预测。深流水线/多发射下它浪费太多性能,于是用运行时信息做动态分支预测。
分支历史表 / 预测缓冲(1 位)
分支预测缓冲(branch prediction buffer,又称分支历史表 branch history table):一小块存储器,用分支指令地址的低位索引,每项存 1 位“上次是否跳转”。取指时查它,按“上次的方向”投机取指。
- 预测只是提示(hint),错了不影响正确性——猜错就删掉错误指令、翻转该位、按正确方向重取。
- 可能因低位地址相同被“别的分支”污染,但同样只影响性能、不影响正确性。
1 位预测器的缺陷:对一个跳 9 次、第 10 次不跳的循环分支——稳态下每轮要错 2 次(最后一次必错,因为预测位还是 taken;下一轮第一次又错,因为上轮退出时把位翻成了 not-taken)。于是“90% 跳转”的分支预测准确率只有 80%(错 2 对 8)。
2 位饱和计数器
为修正上述缺陷,用 2 位预测方案:预测必须连续错两次才改变方向(一个 2 位饱和计数器,预测对就 +1、错就 -1,用中点划分 taken/not-taken 的 4 个状态)。
这样强烈偏向某方向的分支(很多分支如此)只会错 1 次。对上面的循环,2 位预测器准确率回到 ~90%。
分支目标缓冲(BTB)
预测器只告诉你“跳不跳”,还得算目标地址(5 级流水线里要 1 拍 → taken 分支仍有 1 拍代价)。分支目标缓冲(branch target buffer, BTB)用一个带 tag 的 cache 缓存分支的目标 PC(或目标指令),使预测为 taken 时能立刻从目标取指。它比简单预测缓冲更贵(有 tag)。
更强的预测器
- 关联预测器(correlating predictor):同时利用本分支的局部历史和最近若干分支的全局行为——把全局历史当作额外的索引位,能在相同位数下提高准确率。
- 锦标赛预测器(tournament predictor):为每个分支同时维护多个预测器(如一个局部、一个全局),再用一个选择器(类似 1/2 位预测器)挑出近期更准的那个。现代处理器常用这类集成预测器。
减少分支本身:加条件传送指令(如 ARMv8 的
CSEL)——按条件改写目标寄存器而非改 PC,可让程序里的条件分支更少。
复杂流水线:更深与多发射
- 更深流水线:切更多级 →
更短、吞吐更高,但分支/冒险代价(按周期数)更大,更依赖好的预测与旁路。 - 多发射 / 超标量(multiple issue / superscalar):每周期取/发射多条指令以追求 IPC > 1(即 CPI < 1)。这放大了分支错误的代价(每错一拍丢多条指令),因此高性能处理器几乎都配激进的动态分支预测。
方案选择(思考):predict-not-taken / predict-taken / 动态预测,猜对零开销、猜错 2 周期,动态平均准确率 90%。对“5% 跳转”的分支用 not-taken 最好;“95% 跳转”用 taken 最好;“70% 跳转”用动态预测最好。没有万能方案,取决于分支的实际行为——这与缓存配置的结论一致(见 Lec 14)。
实现层面,流水线处理器的旁路与精确控制依赖 EHR,见 Lec 23。