乱序执行的动机与五大部件:为什么必须乱序、怎么做到乱序又不出错
承乱序执行总纲 cpu-out-of-order。本篇回答两个问题:为什么非得乱序(一条慢指令凭什么堵死后面所有人)、乱序怎么做到不出错("顺序进、乱序算、顺序退"的三明治结构,五大部件各司其职)。重命名与顺序退休这两个部件的深入分析,见 ooo-renaming-retirement;和内存重排的边界见 ooo-boundary。
一、为什么要乱序:一条慢指令不该堵死后面所有人
顺序执行(in-order)的致命问题:指令必须按程序序一条条来,前一条没完,后一条即使数据齐了也得干等。 而指令延迟差异巨大(见 cache-organization 第十一节 的周期表):

指令② 和① 毫无数据依赖,本可以在① 等内存的 200 拍里顺手算完。乱序执行就是为此而生:谁的操作数先就绪,谁就先执行,别让程序序绑死执行顺序。
核心动机:用"找出并行"来掩盖"个别指令的长延迟"。现代超标量核有多个执行端口(多个 ALU、load/store 单元),乱序执行就是不断从后面捞出"数据齐了的独立指令"塞进空闲端口,把 IPC(每拍退休指令数)顶上去。
二、怎么做到乱序又不出错:五大部件
乱序执行不是"随便乱跑",而是一套精密机制,保证乱序地算、顺序地对。关键五件套:

整条流水线是**"顺序进、乱序算、顺序退"** 的三明治结构:
| 部件 | 作用 | 关键点 |
|---|---|---|
| 取指/译码 | 按程序序取指令、拆成微操作(µop) | 前端 in-order |
| 寄存器重命名 | 把架构寄存器映射到大量物理寄存器 | 消除假依赖(WAR/WAW),让更多指令能并行——独立剖析 register-renaming |
| 保留站/调度器 | µop 在此等待,操作数一就绪就发射到空闲端口 | 乱序的发生地:谁齐了谁先走——独立剖析 reservation-station |
| 执行端口 | 多个 ALU、乘除、load/store 单元并行执行 | 超标量:一拍能执行多条(LSU 独立剖析 lsu) |
| 重排序缓冲(ROB) | 记录所有在飞指令,强制按程序序退休(commit) | 乱序的收口:结果按程序序对外生效——独立剖析 rob |
三个关键点需要额外强调:
- 前端必须顺序进。取指译码阶段不可能乱序——指令还没翻译完,根本不知道它和前面的指令有什么关系。乱序的自由度是从"指令已知、操作数未知"这个节点才开始有的。
- 乱序发生在发射那一刻。保留站里几十条 µop 排队,谁的操作数齐了谁先发射——这是整条流水线唯一的"乱序发生器"。
- 后端必须顺序退。执行可以乱序,但"对外生效"必须按程序原顺序——这是单核正确性的最后防线(详见 ooo-renaming-retirement 的顺序退休节)。
一句话机制:乱序执行 = 保留站把"按程序序排队"改成"按就绪状态竞争",ROB 把"竞争出来的乱序"重新按程序序排队对外。前端的顺序进保证了指令流可控,后端的顺序退保证了结果正确,中间怎么折腾都行。
五大部件怎么接力:一条 load miss 的旅程
把五件套串起来看,一次 L1 miss 的 load 是这样过完一生的:
- 前端按程序序取出
load r1,[X],译成 µop; - 重命名给目标
r1分配一个物理寄存器(比如 P41),在 RAT 里记下"r1→P41"; - RS 收到 µop,地址操作数就绪后立即发射;
- 执行端口(AGU/LSU)算出地址、发 L1 请求——miss,等 200 拍;
- ROB 里这条 load 一直挂着"未完成",但后面的独立指令不受影响,照样执行、照样退休;
- 200 拍后数据从内存回来,写入 P41,load 完成,ROB 标记可退休;
- 轮到时按程序序退休,
r1的新值正式对外生效。
第 5 步正是乱序的核心收益:load 卡住的是它自己所在的那条程序序位置,而不是它后面的所有指令。 顺序核在第 5 步会整条流水线停摆,乱序核只是这一个槽位挂着——这就是"用并行掩盖延迟"落到具体硬件上的样子。
三、乱序的代价:并行不是免费的
乱序执行能挖并行,但每个部件都有物理上限,成本明明白白:
| 资源 | 上限(典型值) | 满了会怎样 |
|---|---|---|
| RS(保留站) | ~100 条 µop | 前端取指译码被压住,in-order 前端停摆 |
| ROB(重排序缓冲) | 224 条(Skylake) | 在飞指令到上限,新指令进不来,退休速度决定吞吐 |
| 物理寄存器池 | 168~180 个 | 重命名停摆,并行度被压回"名字够用"的水平 |
| 退休带宽 | 4~8 条/拍 | 执行再快,对外生效也要排队,IPC 被退休宽度封顶 |
这些上限相互作用:ROB 是"在飞窗口"的总账,RS 是窗口内的"待命区",物理寄存器是窗口内每条指令的"暂存格"——任何一个满了,前端都会停。
- 分支误预测是最大的单点打击:猜错时 ROB 里整批投机指令全部作废,RS 清空、重命名回滚到 checkpoint——Skylake 上误预测代价约 15~20 拍,连续误预测能把乱序收益吃干净。所以分支预测器的命中率,是乱序执行威力能否兑现的前提(见 branch-prediction)。
- 功耗与面积:几百个物理寄存器 + 每拍多次读写的多端口,是芯片里最贵、最热的结构之一。这也是为什么低功耗小核(Atom/E-core)往往是顺序核——省电比快更重要。
四、乱序的能力边界:什么时候它也救不了
乱序掩盖的是"单个指令的长延迟",掩盖不了三件事:
- 纯依赖链:一串
x = x + 1式的 RAW 链,每条指令必须等上一条算完,乱序无处可挖——IPC 上限就是 1/链上延迟(加法链约 1/3 拍 ≈ 0.33)。优化方向是降低链上的延迟(见 data-hazards-raw)。 - 超过在飞窗口的长延迟:一次 load miss 要 200 拍,ROB 只有 224 条——如果这 200 拍里没有足够多的独立指令填满窗口,窗口就空了,CPU 只能干等。乱序掩盖延迟的能力,上限就是"在飞窗口 × 每拍可发射条数"。
- 端口饱和:所有 load/store 都得挤同一组 LSU 端口,计算指令再多也喂不满,此时瓶颈已经从"延迟"变成"吞吐"。
一句话:乱序执行把"延迟掩盖"做到极致,但掩盖不了依赖链(没东西可并行)、掩盖不了超窗口的长延迟(没窗口可藏)、掩盖不了端口饱和(有窗口没路走)。
五、一句话总结
顺序执行的死穴是"前一条慢、后一条干等"——一条 load miss 能堵死 200 拍的流水线。乱序执行的解法是用"找出并行"掩盖"个别长延迟":谁的操作数先就绪谁先执行,把数据齐了的独立指令塞进空闲端口。它靠"顺序进、乱序算、顺序退"的三明治结构保证不出错——前端顺序取指译码(顺序进)、保留站按就绪状态竞争发射(乱序的发生地)、ROB 强制按程序序退休(正确性的收口)。乱序的自由度从保留站开始、在 ROB 结束,中间执行端口随便并行。五大部件里,重命名负责挖并行(消假依赖)、保留站负责调度、ROB 负责收口——后两者的解剖见 reservation-station 与 rob。