跳转至

控制冒险与分支预测

动机:前端不能等答案

分支在流水线后部才知道是否跳转,取指单元却每拍都需要下一 PC。若每遇分支就停下,深流水和宽发射的大量能力都会闲置。分支预测用历史猜测方向与目标,让前端继续工作。

预测不是改变程序语义。猜对节省时间;猜错必须丢弃错误路径并恢复正确状态。

静态预测

最简单策略是不记录历史:总预测不跳、后向跳转预测跳转、由编译器提供提示等。硬件小、启动立即可用,但无法适应数据相关行为。

循环回边“多数跳转、最后一次不跳”,后向跳转预测通常有效;输入校验等分支则可能完全不同。

一位与两位饱和计数器

一位预测器记住上次结果。循环最后一次不跳会改写状态,下一次进入循环又错一次。

两位计数器有四个状态:强不跳、弱不跳、弱跳、强跳。实际跳转就向“跳”方向移动一格,否则反向移动一格;只有连续两次反常行为才翻转强倾向。它对循环边界更稳定。

可把状态更新写成饱和加减:

\[ state'=\min(3,state+1) \]

表示一次跳转,未跳转则使用饱和减法。最高位可作为预测方向。

方向之外还要预测目标

知道“会跳”还不够,必须知道跳到哪里。分支目标缓冲区(BTB)用当前取指地址索引,缓存目标 PC。函数返回的目标随调用点变化,通常用返回地址栈(RAS)预测。

间接跳转可能有多个目标,例如虚函数或解释器分派,比直接分支更难预测。

利用局部与全局历史

  • 局部历史记录某个分支自己的模式,适合周期行为;
  • 全局历史记录最近多个分支结果,捕捉不同分支之间的相关;
  • 混合预测器让选择器决定哪类预测器更可靠。

历史越长、表越大,潜在准确率越高,但访问延迟、能耗和别名冲突也增长。预测器必须足够快,晚到的准确答案可能不如早到的近似答案。

预测错误的成本

若分支频率为 \(f_b\),预测错误率为 \(p_m\),每次错误惩罚 \(P\) 拍,则控制冒险额外 CPI 近似:

\[ \Delta CPI=f_b p_m P. \]

\(f_b=0.2\)\(p_m=0.05\)\(P=15\) 时:

\[ \Delta CPI=0.15. \]

对于基础 CPI 接近 1 的核心,这已是显著损失。流水越深,\(P\) 往往越大,预测准确率就越重要。

投机执行与恢复

预测路径上的指令可以执行并产生临时结果,但不能过早更新架构状态。简单流水线用 flush 取消错误指令的控制信号;乱序处理器把结果保存在重排序缓冲区,按程序顺序提交。预测错误后恢复正确的寄存器映射和 PC。

“算过但没提交”与数据库事务的回滚非常相似。

限制与安全代价

预测状态依赖历史,性能会随输入变化。更重要的是,错误路径虽然不提交架构结果,却可能改变 cache 等微体系结构状态。攻击者可通过时间差推测秘密,Spectre 类漏洞正利用了这种侧信道。

缓解措施会限制投机、隔离预测状态或插入屏障,通常以性能和复杂度为代价。功能正确不等于信息流安全。

历史联系与改进空间

短流水早期处理器可简单等待;深流水、宽发射让每次停顿损失更多,推动预测器从静态规则发展到相关预测和神经预测。未来改进仍需在准确率、延迟、面积、功耗和安全间平衡。

自测

  1. 两位计数器为何比一位预测器更适合普通循环?
  2. BTB、方向预测器和 RAS 分别回答什么问题?
  3. 为什么错误路径不改变架构状态,仍可能形成安全泄漏?