流水线、冒险与乱序执行
ISA 说"指令按程序顺序执行",真实 CPU 却在并行执行几十条指令——只要最终结果与顺序执行一致。本章从经典五级流水线讲到超标量与乱序执行,并解释分支预测失败的代价与推测执行的安全代价(→ 时序逻辑、时钟与状态机)。
经典五级流水线
Section titled “经典五级流水线”把一条指令的执行切成五个阶段,每个阶段由独立的电路承担:
五个阶段同时工作,理想情况下每个周期都有一条指令完成——吞吐率提高五倍,单条指令延迟不变。这是流水线的普遍规律:它改善吞吐,不改善延迟。每个阶段只做一小部分工作,也让主频得以提高(关键路径变短)。
理想被三种**冒险(hazard)**打破:
- 结构冒险:两条指令要抢同一个硬件部件(如同时访存与取指)。解决:资源复制(分离的指令/数据缓存)。
- 数据冒险:后一条指令依赖前一条的结果。解决:前递(forwarding)——EX 的结果直接送给下一条的输入,不等写回;无法前递时(load-use 相邻)停顿一拍(stall/bubble)。
- 控制冒险:分支指令要到 EX 才知道跳不跳,但取指不能等。解决:分支预测。
流水线越深,分支预测失败要丢弃的指令越多——分支预测失败的代价随流水线深度增长(现代 CPU 预测错误要付 15–20 个周期的冲刷代价)。现代预测器用巨大硬件表记录历史模式,准确率超过 95%,让"每 5 条一条分支"的程序仍能保持流水线满载。程序员的对应手段:让分支可预测(排序后处理、把罕见分支放在前面)。
超标量与乱序执行
Section titled “超标量与乱序执行”超标量 = 每个周期发射多条指令。乱序执行(OoO)= 后面的指令不必等前面的没就绪的指令:
- 取指与译码仍按程序顺序;
- 重命名消除假依赖(不同指令写了同名寄存器,重命名到物理寄存器);
- 发射队列等待操作数就绪,就绪即执行( Tomasulo 算法思想);
- 重排序缓冲(ROB)保证提交仍按程序顺序——异常与中断的语义由此保持。
一句话总结:乱序执行重排的是执行,提交仍按序。这是"承诺不变、机制自由"(→ 指令集架构)在微架构内部的再现。现代大核每周期能发射 4–8 条指令,窗口内同时跟踪数百条。
推测执行的暗面
Section titled “推测执行的暗面”乱序执行依赖推测:分支还没确定就沿着预测路径执行。推测执行只是"预备"——结果留在缓存与 ROB 里,预测错了就丢弃。但丢弃指令不撤销副作用:访问过的数据已经被带进缓存,而缓存命中与未命中的时间差是可以测量的。Spectre 类漏洞正源于此:用推测执行作为侧信道,绕过了进程隔离。修复只能靠缓解(边界检查屏障、缓解模式),根本解法仍在探索(→ 内存安全与常见漏洞)。
- 流水线改善吞吐不改善延迟:五级流水线理想下每周期完成一条指令
- 三种冒险:结构(复制资源)、数据(前递/停顿)、控制(预测)
- 分支预测失败的代价随流水线深度增长;现代预测器准确率超 95%
- 乱序执行重排的是执行,提交仍按序;语义由 ROB 的按序提交保持
- Spectre 类漏洞正源于推测执行的缓存副作用——丢弃指令不撤销副作用