高级流水线技术
基本流水线把 CPI 从
把流水线加深(每段更短、主频更高)——超流水线 把流水线加宽(每拍进来多条)——超标量、超长指令字
加宽这条路上还有第二个问题:谁来找出”哪些指令可以同时做”。 答案有两个——编译器(静态)或硬件(动态)——而这个分岔,就是超长指令字与超标量的全部区别,也是一段真实的技术史。
机制
指令级并行:能挖多少
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] ← 两条同时执行
关键在于”判断彼此无关”这一步由硬件在运行时完成——取指后有一套相关性检测逻辑去比对寄存器号,决定这几条能不能一起发。
代价:检测逻辑的复杂度随发射宽度平方增长(
三、超长指令字 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。
链接
- 🏠 返回总览:计算机组成原理第 5 章:中央处理器总览
- ⬅️ 上一节:5.6.4 流水线的性能指标
- ➡️ 下一节:5.7.1 SISD、SIMD、MIMD 的基本概念
- 🔗 ISA 分层判据:4.1.1 指令集体系结构
- 🔗 Cache 命中的不确定性:3.5.2 Cache 的基本工作原理
- 🔗 精确异常:5.5.3 异常和中断响应过程
- 📖 名词库:第 5 章名词库