mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
3412 字
9 分钟
乱序执行与推测执行
2026-03-18

流水线让指令重叠执行,但顺序流水线有一个根本限制:如果前一条指令卡住了(比如缓存未命中),后面所有指令都必须等待。乱序执行(Out-of-Order Execution, OoO)打破了这一限制——只要后续指令的操作数就绪,就可以先执行,不必等前一条指令完成。

乱序执行是现代高性能 CPU 的核心机制,也是 Spectre/Meltdown 安全漏洞的技术根源。理解乱序执行,就理解了现代 CPU 性能的”秘密武器”和”阿喀琉斯之踵”。

一、为什么需要乱序执行?#

1.1 顺序执行的局限#

考虑以下代码序列:

LD r1, [mem1] ; 加载,缓存未命中,~200 周期
ADD r2, r3, r4 ; 不依赖 r1,可以立即执行
MUL r5, r6, r7 ; 不依赖 r1,可以立即执行
ADD r8, r1, r9 ; 依赖 r1,必须等待

顺序执行时序:

周期: 1 2 ... 200 201 202 203 204
LD: IF ID ... MEM WB
ADD: IF ●●● ●●● ID EX MEM WB
MUL: ●●● ●●● ●●● ID EX MEM WB
ADD(r1): ●●● ●●● ●●● ●●● ID EX
↑ 200 周期气泡!

乱序执行时序:

周期: 1 2 3 4 ... 200 201 202 203
LD: IF ID EX MEM ... MEM WB
ADD: IF ID EX MEM WB ← 提前执行!
MUL: IF ID EX MEM WB ← 提前执行!
ADD(r1): ID EX MEM WB

乱序执行将 200+ 周期的气泡缩短为 0——ADD 和 MUL 在 LD 等待内存时就已经执行完了。

1.2 乱序执行的核心约束#

乱序执行必须遵守一个铁律:程序语义不变。即乱序执行的结果必须与顺序执行完全一致。

实现方式:乱序执行,顺序退休。指令可以乱序执行,但结果必须按程序顺序提交(退休)。

flowchart LR subgraph 顺序["顺序提交"] R1["指令 1 退休"] --> R2["指令 2 退休"] --> R3["指令 3 退休"] end subgraph 乱序["乱序执行"] E3["指令 3 先执行"] E1["指令 1 后执行"] E2["指令 2 最后执行"] end 乱序 --> 顺序 style 乱序 fill:#fff9c4,stroke:#f9a825 style 顺序 fill:#e8f5e9,stroke:#2e7d32

二、寄存器重命名#

2.1 假依赖的问题#

指令之间的依赖关系有三种:

依赖类型示例性质
RAW(Read After Write)ADD r1, r2 → SUB r3, r1真依赖,无法消除
WAR(Write After Read)SUB r3, r1 → ADD r1, r4假依赖(反依赖)
WAW(Write After Write)ADD r1, r2 → MUL r1, r3假依赖(输出依赖)

WAR 和 WAW 是”假依赖”——它们不是因为数据真正依赖,而是因为寄存器名称冲突。寄存器重命名可以消除这些假依赖。

2.2 重命名的原理#

核心思想:将架构寄存器(r0-r31)映射到更多的物理寄存器(p0-p127+),消除名称冲突。

; 重命名前(有 WAR 依赖)
ADD r1, r2, r3 ; r1 = r2 + r3
SUB r4, r1, r5 ; r4 = r1 - r5 (RAW: 依赖 r1)
MUL r1, r6, r7 ; r1 = r6 * r7 (WAR: r1 被 r4 读取后又写入)
; 重命名后(WAR 消除)
ADD p5, p2, p3 ; r1 → p5
SUB p8, p5, p5 ; r4 → p8, 读取 p5(r1 的物理寄存器)
MUL p9, p6, p7 ; r1 → p9(新的物理寄存器!不再与 p5 冲突)

重命名后,MUL 不再阻塞 SUB——它们可以乱序执行。

2.3 寄存器重命名表(RAT)#

RAT(Register Alias Table)维护架构寄存器到物理寄存器的映射:

┌──────────────┬──────────────┐
│ 架构寄存器 │ 物理寄存器 │
├──────────────┼──────────────┤
│ r0 │ p23 │
│ r1 │ p9 │ ← 最近一次写入 r1 的物理寄存器
│ r2 │ p2 │
│ r3 │ p3 │
│ ... │ ... │
│ r31 │ p45 │
└──────────────┴──────────────┘

当一条指令写入架构寄存器 ri 时,RAT 中 ri 的映射更新为新的物理寄存器。旧的物理寄存器仍然保留,直到所有读取它的指令都完成。

2.4 物理寄存器数量的影响#

CPU架构寄存器物理寄存器重命名比
Intel Skylake16 (x86-64)18011.25x
AMD Zen 416 (x86-64)18011.25x
Apple M1 Firestorm31 (AArch64)~35011.3x

物理寄存器越多,能容纳的”在途指令”越多,乱序执行的窗口越大。但物理寄存器文件是 CPU 中面积和功耗最大的组件之一。

三、重排序缓冲区(ROB)#

3.1 ROB 的作用#

ROB(Reorder Buffer)是乱序执行的核心数据结构,负责:

  1. 跟踪指令状态:每条指令在 ROB 中有一个条目
  2. 保证顺序退休:指令按程序顺序从 ROB 中退休
  3. 支持精确异常:异常发生时,ROB 中异常指令之后的所有指令都被丢弃
  4. 支持推测执行回滚:分支预测失败时,ROB 中错误路径的指令被丢弃

3.2 ROB 的结构#

┌─────┬──────────┬─────────┬──────────┬─────────┬──────────┐
│ Seq │ 指令 │ 状态 │ 目标寄存器│ 结果值 │ 异常标志 │
├─────┼──────────┼─────────┼──────────┼─────────┼──────────┤
│ 1 │ ADD r1 │ 完成 │ p5 │ 42 │ 无 │
│ 2 │ LD r2 │ ⏳ 等待 │ p8 │ — │ 无 │
│ 3 │ MUL r3 │ 完成 │ p9 │ 100 │ 无 │
│ 4 │ SUB r4 │ 异常 │ p10 │ — │ 除零! │
│ 5 │ ADD r5 │ 完成 │ p11 │ 7 │ 无 │ ← 推测执行
└─────┴──────────┴─────────┴──────────┴─────────┴──────────┘
指令 2 阻塞了退休
指令 1 可以退休
指令 3-5 必须等指令 2
Note

ROB 是一个循环队列。新指令从尾部入队,完成的指令从头部退休。当 ROB 满时,前端不能发射新指令——这是乱序执行窗口的上限。

3.3 ROB 容量与性能#

CPUROB 条目数意味着
Intel Skylake224最多 224 条指令在途
AMD Zen 4256最多 256 条指令在途
Apple M1 Firestorm~350最多 ~350 条指令在途
Intel Golden Cove512最多 512 条指令在途

ROB 容量直接影响 CPU 隐藏缓存未命中延迟的能力。ROB 越大,CPU 能在等待内存时执行更多后续指令。

3.4 ROB 条目的生命周期#

一条指令在 ROB 中的完整生命周期如下:

stateDiagram-v2 [*] --> 分配: 指令解码后 分配 --> 执行中: 操作数就绪 执行中 --> 写回: 执行完成 写回 --> 等待退休: 结果写入ROB 等待退休 --> 已退休: 前面所有指令已退休 已退休 --> [*] 执行中 --> 异常: 执行出错 异常 --> 回滚: 精确异常处理 回滚 --> [*]: 丢弃后续指令

每个 ROB 条目至少需要记录以下信息:

字段位数(估算)说明
指令标识~7 bit在 128 条指令窗口中定位
目标物理寄存器~8 bit180+ 物理寄存器需要 8 位
结果值64 bit整数或浮点结果
状态标志~4 bit完成/异常/推测等
异常码~8 bit异常类型
总计~91 bit每条目约 12 字节

224 条目的 ROB 大约需要 2.5-3 KB 的存储——这还不包括关联的保留站和物理寄存器文件。

3.5 ROB 与精确异常#

ROB 保证了精确异常(Precise Exception)——当异常发生时,CPU 的状态等同于顺序执行到异常指令时的状态:

// 假设指令 4 触发除零异常
// ROB 状态:
// 指令 1: 已完成 → 退休
// 指令 2: 已完成 → 退休
// 指令 3: 已完成 → 等待退休
// 指令 4: 异常!→ 触发回滚
// 指令 5: 已完成 → 被丢弃(推测执行的结果)
// 指令 6: ⏳ 执行中 → 被丢弃
// 精确异常保证:
// - 指令 1-3 的结果被提交(已退休)
// - 指令 4 触发异常,寄存器状态回滚到指令 3 之后
// - 指令 5-6 的结果被丢弃(虽然在 ROB 中但未退休)
Important

没有 ROB,乱序执行就无法保证精确异常。一些早期的不支持乱序执行的处理器(如早期的顺序流水线)天然具有精确异常,因为指令本身就是顺序完成的。乱序执行打破了这一天然保证,ROB 是恢复这一保证的关键数据结构。

四、保留站与执行单元#

4.1 保留站(Reservation Station)#

保留站是指令等待操作数就绪的”候车室”:

graph TB subgraph 保留站["保留站(Reservation Station)"] RS1["ALU 端口 0<br/>整数/向量"] RS2["ALU 端口 1<br/>整数/向量"] RS3["ALU 端口 2<br/>加载地址"] RS4["ALU 端口 3<br/>存储地址"] RS5["ALU 端口 4<br/>分支"] end subgraph 执行单元["执行单元"] ALU0["ALU 0"] ALU1["ALU 1"] AGU0["AGU 0"] AGU1["AGU 1"] BRU["Branch Unit"] end RS1 --> ALU0 RS2 --> ALU1 RS3 --> AGU0 RS4 --> AGU1 RS5 --> BRU style 保留站 fill:#fff9c4,stroke:#f9a825 style 执行单元 fill:#e8f5e9,stroke:#2e7d32

指令在保留站中等待,直到:

  1. 所有操作数就绪
  2. 对应的执行单元空闲

两个条件都满足时,指令被”发射”到执行单元。

4.2 唤醒机制#

当一条指令执行完毕,结果被广播到所有保留站——这叫”唤醒”(Wakeup)。等待该结果的指令被标记为”操作数就绪”,可以在下一个周期发射。

周期: 1 2 3 4
ADD: [执行中] [完成] ──→ 广播结果
SUB: [等待 r1] [等待 r1] [r1 就绪!] [发射执行]

唤醒逻辑的复杂度随发射宽度呈平方增长,这是超标量 CPU 面积和功耗的主要来源之一。

五、推测执行#

5.1 推测执行的本质#

推测执行(Speculative Execution)是乱序执行的扩展:CPU 不仅执行操作数就绪的指令,还预测未来可能需要的指令并提前执行。

最常见的推测类型:

推测类型预测内容验证时机失败代价
分支推测分支方向和目标分支条件计算完成冲刷流水线
值推测寄存器/内存的值实际值计算完成回滚并重新执行
加载推测加载地址地址计算完成重新加载

5.2 推测执行的完整流程#

sequenceDiagram participant 取指 participant 解码 participant 重命名 participant 保留站 participant 执行 participant ROB 取指->>解码: 取指(可能基于分支预测) 解码->>重命名: 解码为 μop 重命名->>保留站: 重命名后分配到保留站 重命名->>ROB: 在 ROB 中分配条目 Note over 保留站: 等待操作数就绪 保留站->>执行: 操作数就绪,发射执行 执行->>ROB: 写回结果到 ROB 执行->>保留站: 广播结果,唤醒等待指令 alt 分支预测正确 ROB->>ROB: 按顺序退休 else 分支预测失败 ROB->>ROB: 丢弃错误路径的指令 ROB->>重命名: 恢复 RAT 到分支前的状态 取指->>取指: 从正确路径重新取指 end

5.3 推测窗口的大小#

推测窗口 = ROB 容量。在 Intel Skylake 上,ROB 有 224 个条目,意味着 CPU 最多可以向前”看”224 条指令。

如果一条加载指令缓存未命中需要 200 周期,CPU 可以在这 200 周期内执行后续不依赖加载结果的指令——只要它们在 ROB 容量范围内。

六、Spectre 与 Meltdown:推测执行的安全漏洞#

6.1 Spectre V1:边界检查绕过#

// 受害者代码
if (x < array1_size) {
y = array2[array1[x] * 64];
}
// 攻击者:训练分支预测器,使其预测 x < array1_size 为 true
// 然后传入恶意 x 值(超出边界)
// CPU 推测执行了越界读取,虽然结果被丢弃,但缓存副作用保留

攻击步骤:

  1. 训练阶段:多次调用合法 x,使分支预测器”学习”到条件为 true
  2. 攻击阶段:传入越界 x,CPU 推测执行越界读取
  3. 侧信道:推测执行将 array1[x] 的值作为索引访问 array2,不同值导致不同缓存行被加载
  4. 推断:通过 Flush+Reload 探测 array2 的缓存状态,推断出 array1[x] 的值

6.2 Meltdown:特权级绕过#

Meltdown 利用了 Intel CPU 的一个微架构缺陷:在推测执行中,特权级检查被延迟到退休阶段。

// 攻击者代码(用户态)
// 读取内核地址的数据
char data = *(char *)kernel_address; // 应该触发段错误
// 但在段错误之前,CPU 已经推测执行了后续指令
// 后续指令将 data 作为索引访问数组,留下缓存痕迹
flowchart TD A["用户态读取内核地址"] --> B["CPU 推测执行<br/>(特权级检查延迟)"] B --> C["读取内核数据到寄存器"] C --> D["用数据作为索引访问数组"] D --> E["缓存中留下痕迹"] E --> F["特权级检查失败<br/>触发异常"] F --> G["推测结果被丢弃<br/>但缓存痕迹保留!"] G --> H["Flush+Reload 推断数据"] style A fill:#ffcdd2,stroke:#c62828 style G fill:#fff9c4,stroke:#f9a825 style H fill:#e1bee7,stroke:#6a1b9a

6.3 缓解措施与性能影响#

漏洞缓解措施原理性能影响
MeltdownKPTI(Kernel Page Table Isolation)用户态/内核态使用不同页表5-30%
Spectre V1数组边界检查 + lfence在边界检查后插入序列化指令0-10%
Spectre V2Retpoline / IBRS替换间接分支或限制预测器5-30%
Spectre-RSBRSB 填充在上下文切换时填充 RAS0-5%
所有微码更新 + OS 补丁综合缓解5-40%
Warning

Spectre/Meltdown 的缓解措施对 I/O 密集型和系统调用频繁的工作负载影响最大(因为 KPTI 每次系统调用都要切换页表)。对于纯计算密集型工作负载,影响通常 <5%。

七、内存消歧与 Store-to-Load 转发#

乱序执行对整数和浮点指令的效果很好,但内存指令(加载和存储)带来了额外的复杂性:两条内存指令是否冲突,取决于它们的地址——而地址可能在执行时才知道。

7.1 内存消歧(Memory Disambiguation)#

CPU 必须判断一条 Load 指令是否与之前未完成的 Store 指令冲突(访问同一地址):

flowchart TD STORE["ST [addr1] = 42<br/>地址尚未计算"] --> LOAD["LD r1, [addr2]<br/>addr2 也不知道"] LOAD --> Q{"addr1 == addr2?"} Q -->|"相等"| CONFLICT["Load 必须等待 Store<br/>(读-after-写 冒险)"] Q -->|"不等"| INDEPENDENT["Load 可以先执行<br/>(乱序执行生效)"] Q -->|"不知道"| SPECULATE["推测:不等<br/>(大多数情况正确)"] SPECULATE -->|"推测正确"| OK["正常继续"] SPECULATE -->|"推测错误"| ROLLBACK["回滚 Load<br/>重新执行"] style CONFLICT fill:#ffcdd2,stroke:#c62828 style INDEPENDENT fill:#e8f5e9,stroke:#2e7d32 style ROLLBACK fill:#fff9c4,stroke:#f9a825

现代 CPU 使用存储前推测(Load-Store Speculation):假设 Load 与之前的 Store 不冲突,先执行 Load。如果后续发现冲突,则冲刷 Load 并重新执行。Intel 称之为Memory Ordering Buffer (MOB) 的消歧逻辑。

消歧策略原理误判代价
保守(等待)地址未确定时 Load 等待丢失乱序执行机会
激进(推测)假设不冲突,先执行冲刷并重执行(~20 周期)
混合已知地址精确判断,未知地址推测少量误判
Note

实际测量表明,Load-Store 冲突的概率很低(<1%),所以激进推测在统计上是划算的。Intel 的实现中,MOB 会维护一个 Store 地址的”前缀比较”结构,即使地址还没完全计算出来,也可以部分排除冲突。

7.2 Store-to-Load 转发(Store Forwarding)#

当 Load 确实与之前的 Store 访问同一地址时,CPU 不需要等 Store 写入缓存再从缓存读取——它可以直接从 Store Buffer 中转发数据给 Load:

Store Buffer:
┌──────────┬──────────┬─────────┐
│ 地址 │ 数据 │ 大小 │
├──────────┼──────────┼─────────┤
│ 0x1000 │ 42 │ 4 字节 │ ← Store 写入
│ 0x2000 │ 99 │ 8 字节 │
└──────────┴──────────┴─────────┘
LD r1, [0x1000] → 直接从 Store Buffer 读取 42
不需要等 Store 写入 L1 缓存!

Store Forwarding 大幅减少了 Load 的延迟——从等待 Store 写入缓存(~4-10 周期)到直接从 Store Buffer 读取(~1-3 周期)。

7.3 Store Forwarding 失败的情况#

Store Forwarding 并非总是成功。以下情况会导致转发失败,Load 必须等待 Store 写入缓存后才能读取:

失败原因示例额外延迟
大小不匹配Store 8 字节,Load 4 字节(部分转发)~10-15 周期
部分重叠Store [0,4),Load [2,6)~10-15 周期
多个 Store 合并两次 Store 写入同一缓存行的不同部分~5-10 周期
对齐跨越Store 跨缓存行边界~10-15 周期
// Store Forwarding 失败的典型模式
void bad_pattern(int *arr) {
// Store 4 字节
arr[0] = 42; // ST 4 字节到地址 arr
// Load 8 字节(包含 arr[0] 和 arr[1])
long val = *(long*)arr; // LD 8 字节 → 大小不匹配!
// CPU 无法从 4 字节 Store 转发到 8 字节 Load
// 必须等 Store 写入缓存后再读取
}
// 大小匹配的 Store Forwarding
void good_pattern(int *arr) {
arr[0] = 42;
int val = arr[0]; // 同为 4 字节 → 转发成功!
}
Warning

Store Forwarding 失败是隐性的性能陷阱——perf 不会直接告诉你”Store Forwarding 失败了 N 次”。你需要通过 l1d.replacementld_blocks.store_forward 等 PMU 事件间接观察。在 Intel CPU 上,ld_blocks.store_forward 事件统计了因 Store Forwarding 失败而阻塞的 Load 次数。

# 测量 Store Forwarding 失败
perf stat -e ld_blocks.store_forward ./your_program
# 值越高,说明 Store-to-Load 转发失败越频繁

7.4 实际性能影响#

乱序执行、Store Forwarding 和内存消歧的综合效果可以通过以下实验感受:

#include <stdio.h>
#include <time.h>
#define N 100000000
int main() {
int *arr = aligned_alloc(64, N * sizeof(int));
for (int i = 0; i < N; i++) arr[i] = 1;
// 模式 1:独立加载(乱序执行完美重叠)
clock_t start = clock();
int sum = 0;
for (int i = 0; i < N; i += 4) {
sum += arr[i] + arr[i+1] + arr[i+2] + arr[i+3];
}
clock_t end = clock();
printf("独立加载: %.3f\n", (double)(end - start) / CLOCKS_PER_SEC);
// 模式 2:指针追逐(依赖链,乱序执行无效)
int *next = arr;
start = clock();
for (int i = 0; i < 10000; i++) {
next = (int*)(uintptr_t)next[0]; // 每次依赖上一次
}
end = clock();
printf("指针追逐: %.3f\n", (double)(end - start) / CLOCKS_PER_SEC);
// 模式 3:Store 后立即 Load(Store Forwarding)
start = clock();
for (int i = 0; i < N; i++) {
arr[i] = i; // Store
sum += arr[i]; // Load 同一地址 → Store Forwarding
}
end = clock();
printf("Store-Load 转发: %.3f\n", (double)(end - start) / CLOCKS_PER_SEC);
free(arr);
return 0;
}

八、乱序执行的性能上限#

8.1 指令级并行的理论上限#

研究表明,典型程序的指令级并行度(ILP)上限约为:

程序类型ILP 上限限制因素
整数程序2-5分支、数据依赖
浮点程序10-50+更多独立操作
数据库/OLTP1.5-3大量分支和依赖
科学计算20-100+规则的循环结构

8.2 乱序执行窗口的实际利用率#

即使 ROB 有 224 个条目,实际在途指令数通常远小于 224:

因素影响
数据依赖链依赖链上的指令无法并行
缓存未命中长延迟操作阻塞依赖链
分支预测失败冲刷流水线,浪费在途指令
ROB 满载前端无法发射新指令

8.3 乱序执行与内存层次#

乱序执行与缓存层次的交互是性能分析的核心:

// 乱序执行能隐藏多少延迟?
// 假设 L1 命中 3 周期,L2 命中 12 周期,L3 命中 36 周期,DRAM 200 周期
// 场景 1:独立加载(乱序执行可以完美重叠)
for (int i = 0; i < N; i++) {
sum += arr[i]; // 每次加载独立,乱序执行可以重叠
}
// 场景 2:依赖加载(乱序执行无法帮助)
for (int i = 0; i < N; i++) {
idx = arr[idx]; // 每次加载依赖上一次的结果
sum += idx;
}

九、动手实验#

9.1 实验 1:观察乱序执行的效果#

#include <stdio.h>
#include <time.h>
#define N 100000000
int main() {
int *arr = malloc(N * sizeof(int));
for (int i = 0; i < N; i++) arr[i] = i;
// 场景 1:独立操作(乱序执行有效)
int a = 0, b = 0, c = 0, d = 0;
clock_t start = clock();
for (int i = 0; i < N; i += 4) {
a += arr[i]; // 4 个独立累加
b += arr[i+1]; // 乱序执行可以重叠
c += arr[i+2];
d += arr[i+3];
}
clock_t end = clock();
printf("独立操作: %.3f\n", (double)(end - start) / CLOCKS_PER_SEC);
// 场景 2:依赖链(乱序执行无效)
int sum = 0;
start = clock();
for (int i = 0; i < N; i++) {
sum += arr[sum % N]; // 每次依赖上一次结果
}
end = clock();
printf("依赖链: %.3f\n", (double)(end - start) / CLOCKS_PER_SEC);
free(arr);
return 0;
}

9.2 实验 2:用 perf 观察乱序执行指标#

# 查看乱序执行相关的硬件事件
perf stat -e \
instructions,\
cycles,\
uops_issued.any,\
uops_executed.any,\
uops_retired.any,\
int_misc.recovery_cycles \
./your_program
# uops_issued > uops_retired 说明有推测执行被丢弃
# int_misc.recovery_cycles 高说明分支预测失败频繁

9.3 实验 3:ROB 容量的影响#

// 通过制造长依赖链来测试 ROB 容量
#include <stdio.h>
#include <time.h>
int main() {
// 创建一条长依赖链
double x = 1.0;
clock_t start = clock();
for (int i = 0; i < 100000000; i++) {
x = x + 1.0; // 每次迭代依赖上一次
x = x * 0.999;
}
clock_t end = clock();
printf("长依赖链: %.3f 秒, x=%.6f\n",
(double)(end - start) / CLOCKS_PER_SEC, x);
// 短依赖链(4 条独立链)
double a = 1.0, b = 1.0, c = 1.0, d = 1.0;
start = clock();
for (int i = 0; i < 25000000; i++) {
a = a + 1.0; a = a * 0.999;
b = b + 1.0; b = b * 0.999;
c = c + 1.0; c = c * 0.999;
d = d + 1.0; d = d * 0.999;
}
end = clock();
printf("短依赖链: %.3f\n", (double)(end - start) / CLOCKS_PER_SEC);
return 0;
}

十、小结#

上一章探讨了分支预测的原理与代价。

概念要点对软件的影响
寄存器重命名消除 WAR/WAW 假依赖编译器无需关心假依赖
ROB乱序执行、顺序退休精确异常、推测回滚
保留站等待操作数就绪依赖链越短越好
推测执行预测未来指令并提前执行分支预测失败代价大
Spectre/Meltdown推测执行的缓存侧信道安全缓解措施的性能开销

乱序执行和推测执行让 CPU 在指令级并行上榨取了最后的性能。但 ILP 的天花板已经逼近——现代 CPU 的 IPC 很少超过 2.0,远低于 4-wide 发射的理论峰值。未来的性能增长更多依赖于数据级并行(SIMD)和线程级并行(多核),后续章节中讨论。


下一步缓存层次——L1/L2/L3 缓存如何工作?为什么缓存命中率是性能的第一指标?如何写出缓存友好的代码?

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

乱序执行与推测执行
https://blog.souloss.com/posts/cpu-architecture/out-of-order-execution/
作者
Souloss
发布于
2026-03-18
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时