流水线是什么
执行一条指令大致需要几步:取指(Fetch)→ 译码(Decode)→ 执行(Execute)→ 访存(Memory)→ 写回(Writeback)。
如果串行执行,一条指令要占用全部五个阶段,下一条必须等它走完。
流水线的做法:让多条指令同时处于不同阶段——第 1 条在执行时,第 2 条在译码,第 3 条在取指。
就像工厂的装配线:虽然每件产品仍要经过全部工序,但整条线的吞吐率大幅提升。
流水线冒险
结构冒险:两条指令同时需要同一个硬件资源(如都要访问内存)。
数据冒险:后一条指令依赖前一条的结果。
ADD R1, R2, R3 ; R1 = R2 + R3
SUB R4, R1, R5 ; R4 = R1 - R5 ← 依赖上面的 R1第二条必须等第一条写回 R1 才能执行。
控制冒险:遇到分支(if/循环),下一条指令的地址取决于分支结果,在分支确定前无法取指。
应对:转发与乱序
数据转发(Forwarding/Bypassing):不等结果写回寄存器堆,直接在流水线内部把计算结果"转发"给需要的后续指令。
乱序执行(Out-of-Order Execution):
当一条指令被阻塞(如等待数据),后面的不依赖它的指令可以先执行。
这样流水线不会被一条慢指令堵住。
实现要点:
- 指令译码后进入重排序缓冲区(ROB);
- 分析指令间的依赖关系;
- 操作数就绪的指令可以立即发射到执行单元(乱序);
- 执行结果暂存在 ROB;
- 最后按程序顺序提交(Retire/Commit)结果到寄存器和内存。
关键点:乱序执行,顺序提交。这样从外部看,程序的行为与顺序执行完全一致——即使中途有中断或异常,也能保证状态正确。
分支预测
控制冒险的解法。
与其等分支结果,不如猜一个方向继续取指。猜对了,流水线不中断;猜错了,清空流水线重新取指(有惩罚)。
现代 CPU 的分支预测器非常复杂:
- 静态预测:简单的规则(如"向后跳转预测为跳",因为循环通常如此);
- 动态预测:记录历史,用模式匹配。现代预测器使用多级历史表和神经网络启发的方法,准确率可达 95% 以上。
推测执行与安全
推测执行(Speculative Execution):不仅预测分支方向,还提前执行预测路径上的指令。
这带来了严重的安全问题——Spectre 和 Meltdown 漏洞(2018 年公开):
- 推测执行的指令可能访问本不该访问的内存;
- 即使结果最终被丢弃,其副作用(如缓存状态的变化)仍然可被观测;
- 攻击者可以通过侧信道(测量访问时间)推断出敏感数据。
这类漏洞的修复需要硬件和软件协同,并带来了性能损失。
这是一个典型的例子:为性能做的优化,可能引入意想不到的安全后果。
说明:本文为处理器微架构的公开知识整理。