流水线三大冒险:结构、数据、控制

流水线让CPU同时执行多条指令,但指令之间会打架。结构冒险抢硬件,数据冒险抢数据,控制冒险抢方向。这篇聊聊这三种冒险的本质和应对方法。


1. 结构冒险:硬件不够用了

1.1 什么是结构冒险

多条指令同时需要同一个硬件资源[1]。比如:

  • 两条指令都要写寄存器,但寄存器堆只有一个写端口
  • 两条指令都要用除法器,但除法器只有一个
  • 取指和访存同时访问内存,但只有一个内存端口

1.2 MIPS浮点流水线的例子

MIPS的浮点单元有多个功能部件[2]

  • FP Adder:3-4周期
  • FP Multiplier:6-7周期
  • FP Divider:24周期(非流水化)

寄存器写端口冲突

Cycle 11:
  ADD.D F2, F4, F6   ; 完成,要写F2
  MUL.D F8, F10, F12 ; 完成,要写F8
  L.D   F14, 0(R2)   ; 完成,要写F14

三个指令同时到达WB阶段,但FP寄存器堆只有一个写端口[2]。这就是结构冒险。

解决方案

  1. 增加写端口:成本高,利用率低(大部分时间只有一个写)
  2. 检测并停顿:在ID阶段检测冲突,延迟发射
  3. 移位寄存器跟踪:记录未来哪些周期会写寄存器,提前冲突检测

1.3 除法单元的独占

MIPS的FDIV需要24周期,且非流水化[2]

Cycle 1:  FDIV.D F1, F2, F3   ; 开始除法
Cycle 2-24: 其他指令执行,但第二个FDIV必须等
Cycle 25: FDIV.D F4, F5, F6   ; 下一个除法才能开始

这是结构冒险的极端情况——功能部件被长期占用。

解决方案

  1. 流水化除法器:把24周期拆成多级,每级可重叠
  2. 增加除法单元:成本高,除法不频繁
  3. 编译器调度:在除法后面插入无关指令

2. 数据冒险:数据还没准备好

2.1 RAW(Read After Write)

最普遍的冒险。后一条指令读前一条指令要写的结果,但还没写好。

MIPS例子[2][3]

L.D   F4, 0(R2)      ; 加载数据到F4
MUL.D F0, F4, F6     ; 用F4做乘法

L.D在MEM阶段才拿到数据,MUL.D在EX阶段就需要F4。即使转发,也需要停顿1周期[3]

转发(Forwarding/Bypassing)[3][4][5]

  • 把ALU结果直接传给下一条指令的EX输入
  • 把MEM结果传给EX输入
  • 减少但无法完全消除停顿

Load-Use Hazard的特殊性[3]

LW  x1, 0(x2)      ; 加载
ADD x3, x1, x1     ; 立即使用x1

Load数据在MEM阶段末尾才可用,ADD在EX阶段就需要。转发无法解决(不能向过去转发),必须停顿1周期插入NOP[3]

2.2 WAW(Write After Write)

两条指令写同一个寄存器,可能乱序完成导致错误结果。

MIPS浮点例子[2]

ADD.D F2, F4, F6   ; 延迟3周期
L.D   F2, 0(R2)    ; 延迟1周期

L.D比ADD.D快,可能先写F2,然后ADD.D覆盖它。程序期望的是ADD.D的结果。

解决方案

  1. ROB顺序提交:指令按程序顺序从ROB退休,强制ADD.D先写
  2. 寄存器重命名:给每条指令分配不同的物理寄存器,消除WAW[6][7]

2.3 WAR(Write After Read)

后一条指令写前一条指令要读的寄存器。但在流水线中,读总是在ID阶段,写在WB阶段,所以WAR不会自然发生[3]

只有在乱序执行读延后的情况下才可能发生,现代CPU通过寄存器重命名消除[7][8]


3. 控制冒险:分支走哪条路

3.1 分支延迟

5级流水线中,分支在EX阶段才确定目标地址:

Cycle 1: BEQ taken? (EX阶段计算)
Cycle 2: 如果预测错误,已取两条错误指令

分支预测失败的代价

  • 简单流水线:2-3周期
  • 深度流水线(20+级):15-20周期[9][10]

3.2 预测与恢复

静态预测:总是预测不跳转,或编译器标记[10]

动态预测:基于历史记录预测[10]

  • Bimodal:简单的2位饱和计数器
  • Global:考虑全局分支历史
  • Local:考虑每个分支的历史

恢复机制

  • 预测错误时,清空流水线(flush)
  • 从正确地址重新取指
  • ROB丢弃推测执行的指令[6][7]

3.3 精确异常

异常发生时,处理器状态必须与程序顺序一致[6][7][8]。ROB保证:

  • 异常指令前的所有指令已提交
  • 异常指令后的指令未修改状态
  • 可以正确恢复并跳转到异常处理程序

4. 三类冒险的对比

冒险类型 原因 检测阶段 解决方案 代价
结构冒险 资源争用 ID 增加资源/停顿调度 面积/性能
RAW 数据未就绪 ID/EX 转发/停顿/乱序执行 逻辑复杂度
WAW 乱序写回 ID ROB顺序提交/重命名 硬件复杂度
控制冒险 分支方向 EX 预测/延迟槽/ROB 预测失败惩罚

5. 现代处理器的应对

5.1 乱序执行(Out-of-Order)

通过ROB和保留站[7][8]

  • 指令顺序发射(Issue)
  • 乱序执行(Execute)
  • 顺序提交(Commit)

消除WAW/WAR,隐藏RAW延迟。

5.2 推测执行(Speculative Execution)

分支预测后,提前执行后续指令[7]

  • 预测正确:加速执行
  • 预测错误:ROB丢弃结果,恢复状态

5.3 转发网络

多级转发覆盖各种依赖[3][11]

  • EX→EX:ALU结果直接转发
  • MEM→EX:Load数据转发
  • WB→EX:写回阶段转发

6. 总结

流水线冒险的本质是并行执行与程序顺序的冲突

结构冒险:硬件资源有限,需要复制或调度
数据冒险:数据依赖存在,需要转发或等待
控制冒险:程序流程不确定,需要预测和恢复

现代CPU通过乱序执行+ROB+寄存器重命名+分支预测的组合拳,把冒险的影响降到最低。但这些机制增加了硬件复杂度和功耗,是性能与成本的永恒权衡。


参考


  1. GeeksforGeeks. Pipeline Hazards.

  2. Jyoti Prakash Blog. Delving Deeper into the MIPS Pipeline. FP pipeline hazards.

  3. Chipmunk Logic. Designing RISC-V CPU from scratch – Part 3. Pipeline hazards.

  4. Lori Academic Direct. Data hazards examples.

  5. Imperial College London. EIE2 Instruction Architectures & Compilers Lecture 8.

  6. CS Shivi. Precise Exceptions & Register Renaming.

  7. 知乎. ARM体系架构. ROB and precise exceptions.

  8. CSDN博客. CPU乱序执行基础 —— Tomasulo算法及执行过程.

  9. IEEE. Characterizing the branch misprediction penalty.

  10. University of Utah. Lecture 7: Branch prediction.

  11. UPC Commons. Bypassing in pipelines.

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容