# "重排"总纲 —— 三个层次，别混成一件事

> "重排(reorder)"是并发话题里被**严重滥用**的词。人们说"CPU 会重排指令""内存会重排""编译器重排"，听起来像同一件事，其实是**三个不同层次、不同性质、不同防御手段**的机制。最常见的误解，就是把 **CPU 乱序执行(out-of-order execution)** 和 **内存重排(memory reordering)** 当成一回事——**它们是正交的两件事**。


> 本篇是"重排"家族的**总纲**：先把三个层次一次性拆清、给出对照表，再路由到各细分篇。读完这篇再去看细分篇，就不会把"CPU 跑得快"的机制和"多核顺序对不对"的机制搅在一起。

## 一、先破一个误解：CPU 乱序执行 ≠ 内存重排

这是全篇最重要的一句话，先钉死：

| | **CPU 乱序执行(OoO)** | **内存重排(memory reordering)** |
|---|---|---|
| 是什么 | 核内的**性能引擎**：不按程序序、按"操作数就绪"顺序执行指令，填满执行端口 | 多核下**别人观察到的访存顺序**≠ 本核程序序 |
| 作用对象 | **所有指令**（算术、逻辑、分支、访存都乱序）| 只关乎**访存**（load/store）的对外可见顺序 |
| 关注点 | "单核怎么跑得快" | "多核下顺序看得对不对" |
| 单核可见吗 | **不可见**——靠顺序退休(in-order retire)保证单核结果和顺序执行一样 | 单核也不可见（store forwarding 兜住），**只有别的核**能观察到 |
| 主要成因 | ROB / 保留站 / 寄存器重命名 / 投机执行 | **store buffer / invalidate queue**（不是 OoO！）|
| 归哪篇 | [cpu-out-of-order.md](/concepts/microarch/cpu-out-of-order.md) | [memory-reordering.md](/concepts/memory-ordering/memory-reordering.md) |

**两者的关系是"部分交集，而非等同" **：

```plantuml
@startuml
skinparam shadowing false
skinparam rectangle {
  BackgroundColor<<a>> #E3F2FD
  BorderColor<<a>>     #1976D2
  BackgroundColor<<b>> #FFCCBC
  BorderColor<<b>>     #E64A19
  BackgroundColor<<x>> #FFF9C4
  BorderColor<<x>>     #F9A825
}
rectangle "CPU 乱序执行(OoO)\n管所有指令的执行效率\n单核透明" <<a>> as A
rectangle "内存重排\n管多核访存的可见顺序\n主因是 store buffer" <<b>> as B
rectangle "交集:OoO 对 load/store\n的投机重排\n(x86 被 machine-clear 拉回)" <<x>> as X
A -right-> X
X -right-> B
note bottom of A : 有 OoO **不一定**产生内存重排\n(x86 就把它兜住了)
note bottom of B : 有内存重排也**不一定**靠 OoO\n(store buffer 顺序核也会 StoreLoad)
@enduml
```

- **有 OoO 不一定有内存重排**：x86 是深度乱序的，但它把 OoO 对访存的乱序用 machine-clear 拉回 TSO，最终暴露的内存重排几乎只剩 store buffer 造成的 StoreLoad。
- **有内存重排不一定靠 OoO**：哪怕一个**顺序执行**（非乱序）的核，只要有 store buffer，就会有 StoreLoad 重排。内存重排的主因是缓冲区，不是乱序引擎。

> **一句话**：**OoO 是"怎么跑得快"（微架构，单核事），内存重排是"多核看得对不对"（内存模型，多核事）。它们在"访存的投机重排"上有一点交集，但绝不是同义词。** 把这两件事分开，才是理解整个并发内存模型的第一步。

问题：这里要说明一下什么是OoO?

## 二、"重排"的三个层次：一张总图

把"源代码 → 别的核看到的效果"这条链路走一遍，中间有**三道**独立的重排关卡，分属编译期与运行期：

```plantuml
@startuml
skinparam shadowing false
skinparam rectangle {
  BackgroundColor<<src>> #E3F2FD
  BorderColor<<src>>     #1976D2
  BackgroundColor<<c>>   #FFF9C4
  BorderColor<<c>>       #F9A825
  BackgroundColor<<p>>   #E1BEE7
  BorderColor<<p>>       #6A1B9A
  BackgroundColor<<m>>   #FFCCBC
  BorderColor<<m>>       #E64A19
  BackgroundColor<<out>> #C8E6C9
  BorderColor<<out>>     #388E3C
}
rectangle "① 源代码顺序\n(你写的)" <<src>> as SRC
rectangle "【第1层 编译器重排】\n编译期·静态\n优化器调换无依赖语句" <<c>> as C
rectangle "② 机器码顺序" <<src>> as ASM
rectangle "【第2层 CPU 乱序执行】\n运行期·核内\nROB/重命名/投机,按序退休" <<p>> as P
rectangle "③ 核内实际执行顺序\n(单核透明)" <<src>> as EXEC
rectangle "【第3层 内存重排】\n运行期·跨核可见\nstore buffer/invalidate queue" <<m>> as M
rectangle "④ 别的核看到的\n全局可见顺序" <<out>> as OUT
SRC -right-> C
C -right-> ASM
ASM -right-> P
P -right-> EXEC
EXEC -right-> M
M -right-> OUT
note bottom of C : 拦: **编译器屏障**\n(不生成指令)
note bottom of P : 单核透明,x86 靠\nmachine-clear 保证\n**基本不用你管**
note bottom of M : 拦: **内存屏障**\n(mfence/dmb, 真指令)
@enduml
```

**三层各是一件独立的事**，防住一层不等于防住全部（详见第三节表）：

1. **编译器重排**（编译期）：优化器在**单线程 as-if 语义**下调换独立读写、把变量缓存进寄存器。纯静态，**不生成任何机器指令**。
2. **CPU 乱序执行**（运行期·核内）：这就是"CPU 重排"，性能引擎。乱序执行但**按序退休**，**单核完全透明**；对访存的乱序在 x86 被 machine-clear 拉回。
3. **内存重排**（运行期·跨核）：store buffer 让写延迟可见、invalidate queue 让失效延迟生效，导致**别的核**看到的访存顺序被打乱。

问题：这里好像没有看到流水线引起的乱序执行？x86流水线的乱序执行我们不需要care吗？

> 注意第 2 层和第 3 层**都在运行期、都涉及硬件**，最容易被合并成"CPU 重排"一说——但它们成因不同（乱序引擎 vs 缓冲区）、可见性不同（单核透明 vs 跨核可见）、防御不同（无需管 vs 内存屏障）。第一节那张交集图，讲的就是这两层的关系。

## 三、三层对照表：性质、可见性、怎么防

| | 第1层 编译器重排 | 第2层 CPU 乱序执行 | 第3层 内存重排 |
|---|---|---|---|
| **别名** | 编译期重排 | "CPU 重排"/OoO/乱序执行 | 内存序乱序/memory reordering |
| **谁干的** | 编译器优化器 | CPU 乱序引擎(ROB/保留站/重命名) | store buffer + invalidate queue |
| **时机** | 编译期，静态一次性 | 运行期，每次动态 | 运行期 |
| **作用对象** | 所有语句 | 所有指令 | 仅访存的对外可见顺序 |
| **单核可见** | 否(as-if) | 否(顺序退休) | 否(store forwarding) |
| **多核可见** | 是 | 间接(经第3层暴露) | **是**(本质就是多核问题) |
| **x86 上要不要管** | 要 | **基本不用**(machine-clear 兜住) | 要(StoreLoad) |
| **怎么防** | **编译器屏障**`asm volatile("":::"memory")` | 无需单独防 | **内存屏障**`mfence`/`lock`/`dmb` |
| **有无机器指令** | 无 | —— | 有(且更贵) |
| **细分篇** | 本表即概览 | [cpu-out-of-order.md](/concepts/microarch/cpu-out-of-order.md) | [memory-reordering.md](/concepts/memory-ordering/memory-reordering.md) |

三个必须记住的结论：

- **编译器屏障 ≠ 内存屏障**：前者只拦第1层、无指令、零开销、**挡不住 store buffer**；后者拦第3层、是真指令。一个正确的并发原语（`std::atomic` 默认序）**两层都要下**——这是最常见的 bug 来源。
- **第2层(OoO)在 x86 上不用你操心**：乱序引擎投机重排 load，但违反 TSO 的会被 **memory-ordering machine clear** 清空重放（`perf` 的 `machine_clears.memory_ordering`）。它是性能机制，不是你拿屏障去防的东西。
- **三层相互独立、会叠加**：关掉编译器优化，store buffer 照样制造 StoreLoad；没有 store buffer，编译器换序也能制造同样的错。所以要根除必须**编译器屏障 + 内存屏障一起上**。

## 四、路由：想深入哪一层，去哪篇

```plantuml
@startuml
skinparam shadowing false
skinparam rectangle {
  BackgroundColor<<hub>> #E1BEE7
  BorderColor<<hub>>     #6A1B9A
  BackgroundColor<<leaf>> #C8E6C9
  BorderColor<<leaf>>    #388E3C
}
rectangle "本篇\nreordering-overview\n(三层总纲)" <<hub>> as H
rectangle "compiler-reordering.md\n第1层:编译器重排\nas-if/寄存器缓存/volatile/编译器屏障" <<leaf>> as COMP
rectangle "cpu-out-of-order.md\n第2层:CPU 乱序执行的微架构\n流水线/超标量/ROB/重命名/投机/退休\n+为什么它不等于内存重排" <<leaf>> as OOO
rectangle "memory-reordering.md\n第3层:四种内存重排全景\n成因×架构×屏障封堵" <<leaf>> as MEM
rectangle "store-buffer.md / invalidate-queue.md\n第3层的两个成因部件\n(StoreLoad / LoadLoad 等的来源)" <<leaf>> as PARTS
rectangle "atomic.md\n应用:std::memory_order 各档" <<leaf>> as ATOM
H --> COMP
H --> OOO
H --> MEM
MEM --> PARTS
MEM --> ATOM
@enduml
```

- 想搞清**编译器为什么/怎么重排、volatile 管不管用、编译器屏障** → [compiler-reordering.md](/concepts/memory-ordering/compiler-reordering.md)
- 想搞清**"CPU 怎么乱序、为什么单核不出错、它和内存重排的边界"** → [cpu-out-of-order.md](/concepts/microarch/cpu-out-of-order.md)
- 想搞清**四种内存重排、哪种架构允许、用什么屏障** → [memory-reordering.md](/concepts/memory-ordering/memory-reordering.md)
- 想搞清**内存重排的硬件成因** → [store-buffer.md](/concepts/memory-ordering/store-buffer.md)、[invalidate-queue.md](/concepts/memory-ordering/invalidate-queue.md)
- 想搞清**代码里怎么选内存序** → [atomic.md](/concepts/cache/atomic.md)

## 五、一句话总结

> **"重排"是三层独立机制的统称：编译器重排(编译期·静态·编译器屏障拦)、CPU 乱序执行(运行期·核内·单核透明·基本不用管)、内存重排(运行期·跨核可见·内存屏障拦)。其中最该分清的是——CPU 乱序执行是"单核怎么跑得快"的微架构引擎，内存重排是"多核看得对不对"的内存模型问题，两者只在"访存的投机重排"上有一点交集，绝非同一件事。防御要对层下药，编译器屏障和内存屏障各管一层、缺一不可。**
