概述
流水线(Pipeline)是计算机体系结构中最成功的性能优化技术之一。其核心思想是将指令执行分为多个阶段,使多条指令能够并行执行。1
流水线 vs 非流水线
非流水线处理器:每条指令完成后再开始下一条
周期 1 周期 2 周期 3 周期 4 周期 5 周期 6
┌──────┐ ┌──────┐
│取指 │ │译码 │ │执行 │ │访存 │ │写回 │
│ I1 │ │ I1 │ │ I1 │ │ I1 │ │ I1 │
└──────┘ └──────┘ └──────┘ └──────┘ └──────┘
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│取指 │ │译码 │ │执行 │ │访存 │ │写回 │
│ I2 │ │ I2 │ │ I2 │ │ I2 │ │ I2 │
└──────┘ └──────┘ └──────┘ └──────┘ └──────┘
流水线处理器:各阶段并行处理不同指令
周期 1 周期 2 周期 3 周期 4 周期 5 周期 6
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│取指 │ │译码 │ │执行 │ │访存 │ │写回 │
│ I1 │ │ I2 │ │ I3 │ │ I4 │ │ I5 │
└──────┘ └──────┘ └──────┘ └──────┘ └──────┘
理想情况下,k 级流水线的加速比接近 k 倍。
经典 RISC 流水线
经典五级流水线:
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ 取指 │───▶│ 译码 │───▶│ 执行 │───▶│ 访存 │───▶│ 写回 │
│ (IF) │ │ (ID) │ │ (EX) │ │ (MEM) │ │ (WB) │
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
| 阶段 | 全称 | 功能 |
|---|---|---|
| IF | Instruction Fetch | 从指令缓存取指令 |
| ID | Instruction Decode | 译码并读取寄存器 |
| EX | Execute | ALU 执行或地址计算 |
| MEM | Memory Access | 访问数据缓存 |
| WB | Write Back | 结果写回寄存器 |
流水线 Hazards
流水线存在三种类型的冒险(Hazards),会降低实际加速比。
1. 结构 Hazards(结构冲突)
原因:硬件资源不足以支持所有指令同时执行
经典案例:访存和取指同时访问内存
解决方案:
- 分离指令缓存和数据缓存(Harvard 架构)
- 插入流水线停顿
2. 数据 Hazards(数据冲突)
原因:指令依赖于前面指令的结果
类型:
| 类型 | 说明 | 示例 |
|---|---|---|
| RAW | 读后写(真数据依赖) | add $t0, $t1, $t2; sub $t3, $t0, $t4 |
| WAR | 写后读(反依赖) | add $t0, $t1, $t2; sub $t1, $t3, $t4 |
| WAW | 写后写(输出依赖) | add $t0, $t1, $t2; add $t0, $t3, $t4 |
解决方案:
转发(Forwarding / Bypassing)
将结果直接从 ALU 输出传送到需要的位置,无需等待写回。
┌──────────┐
│ ALU │
└────┬─────┘
│
EX/MEM ─┤
│
┌────▼─────┐
│ 转发总线 │
└────┬─────┘
│
┌──────────────┼──────────────┐
│ │ │
ID/EX ─┘ EX/MEM ─┘ MEM/WB ─┘
(操作数A) (操作数B) (操作数B)
流水线停顿(Stall / Bubble)
当转发无法解决时(如 Load 指令),插入气泡等待数据就绪。
正常流水线:
Cycle: 1 2 3 4 5 6 7
lw : [IF] [ID] [EX] [MEM] [WB]
add : [IF] [ID] [EX] [MEM] [WB]
sub : [IF] [ID] [EX] [MEM] [WB]
带停顿的流水线(Load 使用延迟槽):
lw : [IF] [ID] [EX] [MEM] [WB]
add : [IF] [ID] [STALL] [EX] [MEM] [WB]
sub : [IF] [STALL] [ID] [EX] [MEM] [WB]
3. 控制 Hazards(控制冲突)
原因:分支指令改变程序计数器(PC)
分支预测
| 方法 | 准确率 | 复杂度 |
|---|---|---|
| 总是跳转 | 50% | 最低 |
| 动态一跳转 | 60-70% | 低 |
| 两比特饱和计数器 | 85-90% | 中 |
| 分支目标缓冲(BTB) | 90-95% | 高 |
分支延迟槽
在分支指令后放置不依赖分支结果的指令,无论分支是否执行都会执行。
┌─────────┐
│ 分支指令 │
└────┬────┘
│
┌────────▼────────┐
│ 延迟槽指令 │ ← 始终执行
│(不依赖分支结果) │
└────────┬────────┘
▼
┌────────────────┐
│ 分支目标/下一条 │
└────────────────┘
流水线冲突检测与控制
流水线控制逻辑
┌─────────────────────────────────────────────────────────┐
│ 流水线控制单元 │
├─────────────────────────────────────────────────────────┤
│ │
│ IF/ID ─────┬──────────────────────┬──────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ID/EX ───▶│ stall │ forward │ │
│ │ flush │ │ │
│ ▼ ▼ ▼ │
│ EX/MEM ───▶│ │ forward │ │
│ │ │ flush │ │
│ ▼ ▼ ▼ │
│ MEM/WB ───▶│ │ forward │ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ │
│ 输出信号:stall, flush, forward_enable │
└─────────────────────────────────────────────────────────┘
停顿条件检测
// 简化的停顿检测逻辑
if (ID/EX.MemRead &&
(ID/EX.RegisterRd == IF/ID.RegisterRs ||
ID/EX.RegisterRd == IF/ID.RegisterRt)) {
stall = true; // Load-Use Hazard
}超标量与超流水线
超标量(Superscalar)
多个流水线并行取指、译码、执行:
┌─────────────────────────────────────────┐
│ 超标量处理器(2路) │
├─────────────────────────────────────────┤
│ ┌─────────┐ ┌─────────┐ │
│ │流水线 1 │ │流水线 2 │ ← 并行执行 │
│ └────┬────┘ └────┬────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────┐ ┌─────────┐ │
│ │ 发射队列 │ │ 寄存器重命名 │ │
│ └─────────┘ └─────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────────┐ │
│ │ 按序提交 (In-order Commit) │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘
超流水线(Superpipelining)
将流水线级数划分的更细,提高时钟频率:
普通流水线(5级): 超流水线(10级):
┌─────────┐ ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ 取指 │ │IF1│IF2│ID1│ID2│EX1│...│MEM1│MEM2│WB1│WB2│
└─────────┘ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
乱序执行
现代处理器采用乱序执行(Out-of-Order Execution)提高并行度:
┌──────────────────────────────────────────────────┐
│ 乱序执行引擎 │
├──────────────────────────────────────────────────┤
│ │
│ ┌────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 取指/译码 │───▶│ 寄存器重命名 │───▶│ 发射队列 │ │
│ └────────┘ └──────────┘ └────┬─────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │ 保留站/ROB │ │
│ └────────┬────────┘ │
│ │ │
│ ┌──────────────────┼──────────┐│
│ ▼ ▼ ▼│
│ ┌──────────┐ ┌──────────┐ │
│ │ ALU 1 │ │ ALU 2 │ │
│ └────┬─────┘ └────┬─────┘ │
│ │ │ │
│ └─────────┬─────────┘ │
│ ▼ │
│ ┌──────────┐ │
│ │ 提交队列 │ │
│ └──────────┘ │
└──────────────────────────────────────────────────┘
寄存器重命名
消除 WAR/WAW 依赖:
原始序列: 重命名后:
add r1, r2, r3 ──▶ add r1, r2, r3
add r1, r4, r5 ──▶ add r10, r4, r5 // r1 → r10
add r6, r1, r7 ──▶ add r6, r10, r7 // 使用 r10
参考
Footnotes
-
参考《Computer Organization and Design》by Patterson & Hennessy 和《CSAPP》by Bryant & O’Hallaron ↩