Appearance
流水线冒险与解决方案
考情分析
流水线冒险是408考研综合题的核心考点,常见题型:分析指令序列中存在的相关类型、计算需要插入几个气泡、画出时空图。数据冒险尤其是 RAW 相关最为常考。
三类冒险概述
| 冒险类型 | 根本原因 | 常见解决方法 |
|---|---|---|
| 结构冒险 | 硬件资源冲突 | 增加硬件、资源复制 |
| 数据冒险 | 指令间数据依赖 | 转发、阻塞 |
| 控制冒险 | 分支/跳转改变PC | 延迟分支、分支预测 |
结构冒险(Structural Hazard)
定义:两条指令在同一时钟周期争用同一硬件资源。
典型场景:
- lw 指令在 MEM 阶段访问数据存储器,而后续指令的 IF 阶段也需要访问存储器
- 解决:指令存储器和数据存储器分开(哈佛结构),或分时复用(插入气泡)
- 寄存器堆在 WB 阶段写、ID 阶段读同时发生
- 解决:寄存器堆支持前半周期写、后半周期读(写优先),或转发
数据冒险(Data Hazard)
定义:后续指令需要用到前面指令尚未产生的结果。
三种数据相关类型
| 类型 | 全称 | 定义 | 示例 |
|---|---|---|---|
| RAW(写后读) | Read After Write | 指令 j 读取指令 i 将要写入的寄存器 | I1: ADD R1,R2,R3 → I2: SUB R4,R1,R5 |
| WAR(读后写) | Write After Read | 指令 j 写某寄存器,而指令 i 还需要读它的旧值 | I1: ADD R3,R1,R2 → I2: SUB R1,R4,R5 |
| WAW(写后写) | Write After Write | 指令 j 和指令 i 写同一寄存器,程序语义要求 j 的结果是最终值 | I1: ADD R1,R2,R3 → I2: SUB R1,R4,R5 |
顺序流水线中只有 RAW 是实际问题——因为读操作(ID阶段)总在写操作(WB阶段)之前,后面指令的 ID 可能比前面指令的 WB 更早执行。
乱序(非按序)流水线中三种相关都可能出现:
- WAR:若 I2 因乱序先完成写回,I1 就会读到错误的新值(本该读旧值)
- WAW:若 I1 因乱序后完成写回,R1 的最终值变成 I1 的结果而非 I2 的(违反程序语义)
WAR 和 WAW 也被称为"名相关"(name dependence),因为它们的本质不是数据流依赖,而是两条指令恰好使用了同一个寄存器名。可以通过寄存器重命名技术消除——将逻辑寄存器映射到不同的物理寄存器,使得两条指令实际操作不同的存储位置。
RAW 示例
I1: ADD R1, R2, R3 # R1 = R2 + R3,EX阶段结束(第3周期)才有结果
I2: SUB R4, R1, R5 # 需要读 R1(ID阶段在第3周期,但R1还没写回)若不处理,I2 在 ID 阶段读到的 R1 是旧值,结果错误。
解决方案一:阻塞(Stall / 插入气泡)
在 I2 的 ID 阶段之前插入"气泡"(NOP),等 I1 的结果写回后再让 I2 继续。
I1: IF ID EX MEM WB
I2: IF ** ** ID EX MEM WB (* 表示气泡)- 对 RAW 相关,通常需要插入 2 个气泡(若无转发)
- 优点:实现简单;缺点:性能损失大
解决方案二:数据转发(Forwarding / Bypassing)
将 ALU 运算结果直接从 EX/MEM 阶段的流水线寄存器旁路(bypass)给下一条指令的 EX 阶段,不等写回寄存器堆。
I1: IF ID EX →MEM WB
I2: IF ID EX ↑ (EX阶段从旁路获取I1的EX输出)大部分 RAW 相关可通过转发解决,不需要插入气泡。
例外:lw-use 相关(load-use hazard)
I1: lw R1, 0(R2) # R1的值在MEM阶段结束后才有
I2: ADD R3, R1, R4 # I2的EX阶段需要R1lw 的数据在 MEM 阶段才得到,而 I2 的 EX 阶段在 MEM 之前,转发来不及。此时必须插入 1 个气泡:
I1: IF ID EX MEM →WB
I2: IF ID ** EX ↑ MEM WB (* 表示1个气泡)控制冒险(Control Hazard)
定义:遇到分支或跳转指令时,流水线不知道下一条指令从哪里取,已经取入流水线的后续指令可能是错误的。
问题示意
BEQ R1, R2, target # IF ID EX ...(EX阶段才确定是否跳转)
I2: # 已经开始 IF(可能取错)
I3: # 已经开始 IF(可能取错)解决方案一:延迟分支(Delayed Branch)
将分支指令之后的一条或多条指令定义为"延迟槽",这些指令无论分支是否发生都会执行。编译器负责将有用指令填入延迟槽(或填 NOP)。
解决方案二:分支预测
静态预测:
- 总是预测分支不发生(Always-Not-Taken):适合前向分支(if 条件跳转,通常不跳转)
- 总是预测分支发生(Always-Taken):适合后向分支(循环回边,通常跳转)
- 基于跳转方向预测(向后跳转预测发生,向前跳转预测不发生)——综合了以上两者
动态预测:
- 1位预测器:记录上次分支是否发生,本次按上次结果预测
- 2位预测器(饱和计数器):状态机有4个状态(强发生/弱发生/弱不发生/强不发生),需要连续预测错误两次才改变预测方向
2位预测器状态机:
强发生(11) ←→ 弱发生(10) ←→ 弱不发生(01) ←→ 强不发生(00)
↑发生 发生↑↓不发生 发生↑↓不发生 ↓不发生预测错误时:将已取入流水线的错误指令清除(flush),性能损失约等于分支延迟槽的数量。
交互可视化
例题
例1:以下指令序列在五段式流水线中,假设无旁路机制,SUB需要暂停几个周期?
ADD R1, R2, R3
SUB R4, R1, R5解:ADD 在 T5(WB) 写回 R1,SUB 原本在 T3(ID) 读 R1。ID 必须等 WB 完成后才能读到正确值(若寄存器堆支持同周期前写后读,则 SUB 的 ID 可在 T5 执行)。原本 SUB 的 ID 在 T3,推迟到 T5,暂停 2个周期。
例2:以下指令序列,有旁路机制时是否需要暂停?
LW R1, 0(R2)
AND R3, R1, R4解:LW 的 MEM 阶段结束时(T4末)数据才可用,AND 的 EX 阶段开始时(T4初)就需要 R1。时序上差一个周期,旁路无法解决。仍需暂停 1个周期,之后从 MEM/WB 寄存器旁路取得数据。
例3:某程序分支指令占 20%,采用2位预测器准确率 90%,预测错误代价 2 个周期。求因分支导致的额外 CPI。
解:额外 CPI =
若基础 CPI = 1,则实际 CPI = 1.04。
易混淆知识点
1. RAW/WAR/WAW 什么时候需要处理?
按序流水线只需处理 RAW(因为 ID 在 WB 之前,后面指令可能提前读)。WAR 和 WAW 在按序流水线中不会出现问题——因为指令严格按程序顺序经过各阶段,写操作的先后顺序天然正确。只有在乱序执行的处理器中,WAR 和 WAW 才会成为真正的问题。
2. 转发能解决所有 RAW 吗?
不能。lw-use 相关(load 后面紧跟使用该数据的指令)必须插入 1 个气泡,因为 lw 在 MEM 阶段末尾才拿到数据,而下一条指令的 EX 阶段在同一周期开始,时序上来不及。
3. 分支预测错误的代价到底是几个周期?
取决于分支结果在哪个阶段确定。若在 EX 阶段确定(标准五段),已取入 2 条错误指令,代价 = 2 周期。若硬件将分支判断提前到 ID 阶段,代价降为 1 周期。
考点清单
- 顺序流水线中实际需要处理的只有 RAW 和控制冒险
- 乱序流水线中 RAW/WAR/WAW 都可能出现,WAR 和 WAW 可通过寄存器重命名消除
- lw-use 相关是数据冒险中唯一转发也无法完全消除的,必须插入1个气泡
- 一般 RAW 相关(ALU指令间)可以通过转发解决,不需要气泡
- 2位预测器优于1位:如循环体最后一次迭代预测错误,但不会影响下次循环开始的预测
- 结构冒险通过分离指令存储器和数据存储器解决(这就是为什么流水线CPU使用哈佛结构)