高级流水线技术

基本流水线把 CPI 从 降到了 1。这一节问的是:能不能降到 1 以下。

的意思是一个时钟周期完成不止一条指令——这只能靠”同时做多条”,而”同时做多条”只有两条路可走:

把流水线加深(每段更短、主频更高)——超流水线 把流水线加宽(每拍进来多条)——超标量、超长指令字

加宽这条路上还有第二个问题:谁来找出”哪些指令可以同时做”。 答案有两个——编译器(静态)或硬件(动态)——而这个分岔,就是超长指令字与超标量的全部区别,也是一段真实的技术史。

机制

指令级并行:能挖多少

IPC(每周期指令数)是这一节更常用的量。 基本五段流水线的理想 IPC 是 1,本节所有技术都在往上抬它。

能抬多高,取决于程序里到底有多少条指令彼此无关。 这个上限叫指令级并行度(ILP),由程序本身决定,硬件只能尽量逼近,不能创造。

一、超流水线:把段切得更细

做法:把 5 段切成 10 段、20 段,每段更短 → 更小 → 主频更高。

但5.6.4 已经算过它的天花板:

段间寄存器的固定开销 卡死了上限。 而且分段越细:

  • 分支预测失败的代价越大(要冲刷的段更多)
  • 数据相关的距离越长(转发路径更复杂)
  • 功耗随主频超线性上升

历史结局:Pentium 4 把段数推到 20 级以上、主频冲上 3.8 GHz,然后撞墙——分支预测一旦失败要浪费 20 拍,功耗也失控。之后的处理器普遍回退到 14~19 级并转向加宽。“超流水线”是一条走到头的路。

注意超流水线不改变 IPC——它的理想 IPC 仍是 1,靠的是把 压小。它作用在性能公式的第三个因子上,不是第二个。

二、超标量:硬件每拍发射多条

做法:配置多套功能部件(多个 ALU、多个访存端口),每拍从指令流里取出多条、判断彼此无关后同时发射。

拍1: [I1 IF][I2 IF]
拍2: [I1 ID][I2 ID][I3 IF][I4 IF]
拍3: [I1 EX][I2 EX][I3 ID][I4 ID]   ← 两条同时执行
理想发射宽度

关键在于”判断彼此无关”这一步由硬件在运行时完成——取指后有一套相关性检测逻辑去比对寄存器号,决定这几条能不能一起发。

代价:检测逻辑的复杂度随发射宽度平方增长( 条指令两两比较是 对)。这是超标量宽度上不去的根本原因,主流停在 4~8 发射。

三、超长指令字 VLIW:编译器打包

做法:把多条可并行的指令在编译时打包成一条”超长指令”,硬件拿到后无条件地把各字段送进对应的功能部件。

整数运算浮点运算访存分支一条超长指令,比如位

硬件不做任何相关性检测——它信任编译器已经保证了这几个字段互不相关。填不满的槽由编译器塞 nop。

超标量超长指令字 VLIW
谁找并行硬件,运行时编译器,编译时
指令格式普通指令,多条一条超长指令,多个字段
硬件复杂度高(相关性检测 )低
编译器复杂度低极高
代码密度好差(nop 填槽)
二进制兼容好——换代不影响差——功能部件配置一变,程序全要重编译

“二进制兼容”这一行是致命的。 VLIW 把功能部件的数量和种类暴露进了 ISA——这直接违反了 4.1.1 那条判据:“改了实现,老程序还能不能跑”。VLIW 的答案是”不能”,所以它的 ISA 与微体系结构的分层是破的。

四、动态调度(乱序执行):本节的主角

超标量还有一个静态方案不可能解决的问题:有些相关性在编译时根本无法知道。

LW  R1, 0(R2)      ← 这一条要几拍?
ADD R3, R1, R4     ← 取决于上一条是否 Cache 命中

Cache 命中要 1 拍,未命中要几十上百拍(3.5.2)。编译器在编译时无法知道这一次是命中还是未命中——它连程序会在哪台机器上跑、Cache 多大都不知道。

这就是动态调度存在的全部理由:只有运行时才知道的东西,只能在运行时处理。

乱序执行(out-of-order execution)的做法:

阶段顺序性说明
取指、译码、发射按序保持程序顺序进入
执行乱序操作数一就绪就送去执行,不等前面的指令
提交(写回体系结构状态)按序靠重排序缓冲 ROB 恢复程序顺序

“按序发射、乱序执行、按序提交”这三句是乱序处理器的骨架。

为什么提交必须按序——这是最关键的一问:为了精确异常(5.5.3)。如果乱序提交,一条晚发射的指令可能已经改了寄存器,而它前面那条指令才刚触发缺页——操作系统看到的现场就是”一半新一半旧”的,那条缺页的指令再也无法正确重启。

乱序执行按序提交既拿到并行,又保住语义

这是整个现代处理器设计里最漂亮的一个折中:内部怎么乱都行,只要对外表现得像是严格顺序执行的。这正是 4.1.1 那条 ISA / 微体系结构分层能容纳的极限——微体系结构可以任意激进,只要 ISA 层面观察不到。

五、寄存器重命名

乱序执行会激活两种在顺序流水线里不存在的相关(5.6.3):

I1: ADD R1, R2, R3
I2: SUB R2, R4, R5     ← WAR:I2 写 R2,I1 读 R2
I3: MUL R1, R6, R7     ← WAW:I3 和 I1 都写 R1

这两种相关是”假”的——它们不是真正的数据流动,只是因为寄存器名字不够用而被迫复用造成的冲突。

解决办法:给硬件准备远多于 ISA 寄存器数量的物理寄存器,每次写入都分配一个新的物理寄存器。

有个寄存器物理上有个

重命名之后 WAR 和 WAW 全部消失,只剩下真正的 RAW。

这里有一个值得停一下的观察:4.4.1 说过 x86 只有 8 个(后来 16 个)通用寄存器,远少于 RISC 的 32 个,这曾被视为 x86 的重大缺陷。寄存器重命名基本抵消了这个缺陷——程序员看到的 16 个只是名字,硬件底下有一百多个真实寄存器在轮转。

“ISA 层面的寄存器是名字,不是硬件”——这是 5.1.3 那条”ISA 状态 vs 微体系结构状态”最极端的一个例子。

六、把这一节串成一条线

疑问点:流水线、乱序、指令重排、分支预测、超标量、超长指令字,这些再深入讲讲——反正就是现代 CPU 的优化

“就是现代 CPU 的优化”这个概括是对的,但把它们摆成一张清单就看不出关系了。用两个维度一摆,六个词自己会站好队。

维度一:往深里挖还是往宽里挖。

做法动的是性能公式哪一项天花板
加深(超流水线)段切得更细,主频更高 ↓段间寄存器开销 + 分支代价
加宽(超标量、VLIW)每拍发射多条 ↓ 到 1 以下程序自身的 ILP + 检测逻辑

两条路都有硬天花板,而且都撞上了。 这就是 2005 年之后主频停在 3~4 GHz、转向多核的原因——指令级并行挖完了,只能去挖线程级并行(5.7)。

维度二:谁来找并行。

编译时(静态)运行时(动态)
找并行VLIW、指令调度/重排超标量 + 乱序执行
猜分支静态分支预测(总是不转、向后转就转)动态分支预测(BHT、2 位预测器)
消除假相关编译器换寄存器寄存器重命名

左右两列做的是同一批事,差别只在什么时候做。 而这一栏的胜负,是有明确结论的:

编译时的优势:可以看到整个程序、可以花很长时间分析、不占硬件、不耗电。 运行时的优势:能看到编译时看不到的东西。

而”编译时看不到的东西”里,最要命的一个就是 Cache 是否命中。 一条 LW 是 1 拍还是 200 拍,编译器无从知道——它甚至不知道程序会在哪台机器上跑。 编译器只能按最坏情况保守排布,或者按最好情况排布然后经常猜错。

这一条不对称,决定了历史的走向:

Intel 押注静态方案,做了 Itanium(IA-64),一个纯粹的 VLIW 架构,指望编译器把并行全部找出来。 结果是编译器做不到——不仅 Cache 行为不可知,指针别名分析、动态分支行为也都不可知。Itanium 失败了。 而同时期的 x86 用超标量 + 乱序 + 动态预测,靠硬件在运行时解决同样的问题,活到了今天。

但这不是”动态全面胜利”。 现代处理器实际上两条都用:

  • 编译器仍然做指令调度(5.6.3 那第四种手段),把明显无关的指令排开
  • 硬件再在运行时做一次,处理编译器看不到的部分
  • GPU 和 DSP 至今大量采用 VLIW 思想——因为它们的负载规整、访存模式可预测,编译时看不到的东西少

判据可以收成一句:并行度在编译时能确定多少,就交给编译器多少;剩下的交给硬件。 负载越规整,静态方案越有效;负载越不可预测,动态方案的优势越大。


最后把六个词按”它解决什么问题”再排一次,这是最实用的一张表:

技术解决什么问题属于
基本流水线部件空闲5.6.1~5.6.2
数据转发RAW 停顿5.6.3
指令重排 / 调度RAW 停顿(编译时)5.6.3
分支预测控制冒险5.6.3 + 本节
超流水线 不够小本节
超标量IPC 上不去(硬件找并行)本节
超长指令字IPC 上不去(编译器找并行)本节
乱序执行编译时不可知的延迟(尤其 Cache 未命中)本节
寄存器重命名乱序带来的 WAR / WAW本节

408 的考纲覆盖前七行(超标量、超长指令字、超流水线是明确要求的三个);乱序执行和寄存器重命名属于拓展,但它们是理解”为什么按序提交""为什么 x86 寄存器少却不慢”的关键,值得知道。

三种”超”的对比

这是本节的必考表。

超标量超流水线超长指令字
怎么提高性能空间上重复部件,每拍发多条时间上细分,每段更短编译时打包多条
理想 IPC(= 发射宽度)仍是 1
动性能公式哪一项 ↓ ↓ ↓
谁找并行硬件——编译器
对编译器的要求低低极高
二进制兼容好好差

“超流水线的 IPC 仍是 1”是这张表里最容易错的一格——它靠的是主频,不是并行发射。

边界

层次辨析:动态流水线 / 动态调度

中文都带”动态”,是两个不同的概念:

  • 动态流水线(5.6.1 的分类):多功能流水线的各段可同时按不同功能连接
  • 动态调度 / 乱序执行(本节):硬件按运行时依赖打乱指令执行顺序

前者说的是”段的连法”,后者说的是”指令的顺序”。

边界辨析:超标量 / 超流水线

  • 超标量:加宽,IPC ,靠重复功能部件
  • 超流水线:加深,IPC 仍是 1,靠提高主频

一道题问”哪种技术使 CPI 小于 1”,答超标量和超长指令字,不含超流水线。

边界辨析:为什么必须按序提交

为了精确异常。 乱序提交会让异常发生时的现场变成”一半新一半旧”,故障类异常就无法重新执行当前指令,虚拟存储器随之失效。

口诀:乱序执行、按序提交。

边界辨析:VLIW 为什么破坏 ISA 分层

它把功能部件的数量和种类写进了指令格式,于是换一代硬件老程序就跑不了——正好踩中 4.1.1 那条判据的反面。

超标量把这些细节留在微体系结构里,所以二进制兼容。

对照速查

技术IPC主频谁找并行408 范围
基本流水线1————✅
超流水线1↑——✅
超标量——硬件✅
超长指令字——编译器✅
乱序执行提高实际 IPC——硬件拓展
寄存器重命名消除假相关——硬件拓展
说法判断
超标量能使 CPI 小于 1✅
超流水线能使 CPI 小于 1❌ IPC 仍是 1
超长指令字由编译器发掘并行✅
超长指令字对编译器要求很高✅
乱序执行的提交也是乱序的❌ 必须按序提交
寄存器重命名解决 RAW❌ 解决 WAR 和 WAW
段数越多性能越高❌ 有天花板

考点

  • 三种”超”的对比:必考表。超流水线不改变 IPC 是最常错的一格。
  • 哪些技术使 CPI < 1:超标量、超长指令字、多发射,不含超流水线。
  • 超长指令字对编译器的要求:极高,且二进制不兼容。
  • 动态流水线 vs 动态调度:名字像,概念不同。
  • 乱序执行为什么按序提交:答精确异常。
  • 寄存器重命名解决哪种相关:WAR 和 WAW,不是 RAW。

链接