# 内存重排全景 —— 四种重排 × 成因 × 架构

> 本篇是"重排"总纲 [reordering-overview.md](/concepts/memory-ordering/reordering-overview.md) 的**第3层（内存重排）**细分篇。前面 [store-buffer.md](/concepts/memory-ordering/store-buffer.md) 讲了 **StoreLoad** 一种重排、[invalidate-queue.md](/concepts/memory-ordering/invalidate-queue.md) 讲了它引发的读侧可见性乱序、[atomic.md](/concepts/cache/atomic.md) 提了内存屏障——但"内存重排"这个话题本身横跨这几篇,一直缺一张**总图**。本篇就是那张总图。


> ⚠️ 先分清:本篇讲的**内存重排**（跨核可见顺序）和 **CPU 乱序执行**（[cpu-out-of-order.md](/concepts/microarch/cpu-out-of-order.md)，单核性能引擎）**不是一回事**——详见总纲 [reordering-overview.md](/concepts/memory-ordering/reordering-overview.md) 第一节。


> 核心澄清:经典内存重排就是 **4 种**(LoadLoad / LoadStore / StoreLoad / StoreStore),不是"StoreLoad 又分几种"。真正的复杂度在另一个维度——**同一种重排可能由不同硬件部件造成,且各 CPU 架构允不允许它各不相同**。所以本篇的主线是一张二维矩阵:`4 种重排 × (成因 / 架构 / 怎么封堵)`。


> 分工:**部件怎么造成重排**看分篇([store-buffer.md](/concepts/memory-ordering/store-buffer.md)/[invalidate-queue.md](/concepts/memory-ordering/invalidate-queue.md));**四种重排的全景、跨架构对比、屏障如何逐一对应**看本篇。

## 一、什么叫"重排":程序序 vs 内存序

先把概念钉死。一段代码里内存操作的书写顺序叫**程序序(program order)**;而其他核**实际观察到**这些操作生效的顺序叫**内存序(memory order)**。两者不一定相等——中间隔着编译器、乱序流水线、store buffer、invalidate queue 好几层,每层都可能让"别人看到的顺序 ≠ 你写的顺序"。

```plantuml
@startuml
skinparam shadowing false
skinparam rectangle {
  BackgroundColor<<src>> #E3F2FD
  BorderColor<<src>>     #1976D2
  BackgroundColor<<mid>> #FFF9C4
  BorderColor<<mid>>     #F9A825
  BackgroundColor<<obs>> #FFCDD2
  BorderColor<<obs>>     #C62828
}
rectangle "程序序\n(你写的顺序)\nA; B;" <<src>> as SRC
rectangle "① 编译器重排\n(静态,编译期)" <<mid>> as C
rectangle "② 流水线乱序执行\n(动态,运行期)" <<mid>> as P
rectangle "③ store buffer / invalidate queue\n(可见性延迟,运行期)" <<mid>> as S
rectangle "内存序\n(别的核看到的顺序)\n可能是 B; A;" <<obs>> as OBS
SRC -right-> C
C -right-> P
P -right-> S
S -right-> OBS
note bottom of C : 靠**编译器屏障**拦\n(无 CPU 指令)
note bottom of S : 靠**内存屏障指令**拦\n(真指令,有开销)
@enduml
```

> **关键**:"重排"不是一个机制,而是**多层机制叠加后的现象**。同一种"LoadLoad 被重排"可能来自编译器、也可能来自乱序执行、还可能来自 invalidate queue——**封堵手段也因成因而异**(编译器屏障 vs 硬件屏障)。这是理解一切的前提,下面先分清四种,再分清成因。

## 二、四种重排:两两操作的 4 种先后颠倒

任意**两条**内存操作,按"前一条是读还是写、后一条是读还是写"排列组合,只有 4 种。所谓"某种重排",就是**这一对的先后顺序对外被颠倒**:

| 重排类型 | 程序序 | 被重排后(对外可见) | 一句话 |
|---------|--------|-------------------|--------|
| **LoadLoad** | `读A; 读B;` | 别人看 `读B` 的效果先于 `读A` | 两个读被换序 |
| **LoadStore** | `读A; 写B;` | `写B` 的效果先于 `读A` 完成 | 读还没做完,后面的写先生效了 |
| **StoreStore** | `写A; 写B;` | 别人先看到 `B` 的新值、后看到 `A` 的 | 两个写被换序 |
| **StoreLoad** | `写A; 读B;` | `读B` 先执行、`写A` 的新值还没对别人可见 | 写被拖后、读被提前 |

记忆法:**名字 = "前操作类型 + 后操作类型"**,`XY 重排` 就是"程序序里 X 在前 Y 在后,却被颠倒成 Y 先生效"。

```plantuml
@startuml
skinparam shadowing false
skinparam rectangle {
  BackgroundColor<<r>> #E1F5FE
  BorderColor<<r>>     #0277BD
  BackgroundColor<<w>> #FFEBEE
  BorderColor<<w>>     #C62828
}
rectangle "程序序里,前一条是…" as H
rectangle "读(Load)" <<r>> as L
rectangle "写(Store)" <<w>> as S
rectangle "…后一条是读 → **LoadLoad**\n…后一条是写 → **LoadStore**" <<r>> as LX
rectangle "…后一条是读 → **StoreLoad**\n…后一条是写 → **StoreStore**" <<w>> as SX
L -right-> LX
S -right-> SX
note bottom of LX : Load 打头的两种
note bottom of SX : Store 打头的两种
@enduml
```

这 4 种里,**StoreLoad 最"顽固"**(几乎所有架构都允许,因为它来自 store buffer 这个人人都有的部件),**StoreStore/LoadLoad 次之**(强模型禁、弱模型允),**LoadStore** 各架构差异也大。下一节把"允不允许"按架构摊开。

## 三、成因归属:每种重排是谁造成的

这是本篇最有价值的一张表——**把 4 种重排映射到具体硬件部件**,你就知道"要防它该用哪种屏障、以及为什么 x86 上大多不用防"。

| 重排 | 主要成因(硬件部件/机制) | x86-TSO | ARM/Power(弱序) | 对应分篇 |
|------|------------------------|:---:|:---:|---------|
| **StoreLoad** | **store buffer**:写压在 buffer 没提交,后面的读先打到缓存 | ✅ 允许 | ✅ 允许 | [store-buffer.md](/concepts/memory-ordering/store-buffer.md) §4 |
| **StoreStore** | store buffer **非 FIFO 提交**(弱序)/写合并 | ❌ 禁(FIFO) | ✅ 允许 | [store-buffer.md](/concepts/memory-ordering/store-buffer.md) §4.3 |
| **LoadLoad** | **invalidate queue**(失效延迟生效,读到旧值)+ 投机乱序执行读 | ❌ 禁 | ✅ 允许 | [invalidate-queue.md](/concepts/memory-ordering/invalidate-queue.md) §3-4 |
| **LoadStore** | 乱序执行:读未完成时后面的写先提交 | ❌ 禁 | ✅ 允许 | [invalidate-queue.md](/concepts/memory-ordering/invalidate-queue.md) §4 |

从这张表能一眼看出几件事:

1. **x86 只放行 StoreLoad 一种**——因为 x86 的 store buffer 是 FIFO(挡住 StoreStore)、读不被暴露地投机重排(machine-clear 拉回,挡住 LoadLoad/LoadStore)。**这就是"x86 是强内存模型 TSO"的全部含义**:唯一漏网的是 store buffer 造成的 StoreLoad。
2. **ARM/Power 四种全放行**——store buffer 不保证 FIFO、invalidate queue 乱序对软件可见、读可投机重排。所以同一段无屏障代码"x86 上碰巧对、ARM 上偶发错"。
3. **StoreLoad 是唯一"人人都有"的重排**——因为 store buffer 是几乎所有现代 CPU 的标配,没人愿意为了消灭 StoreLoad 而砍掉 store buffer(那等于回到"写就干等"的年代)。所以连最强的 x86 也留着它。

> **一句话**:x86-TSO = "只有 StoreLoad"、ARM/Power = "四种都有"。差异的根源不是"谁更先进",而是**弱模型把更多硬件缓冲的乱序直接暴露给软件(换更高性能),让软件用屏障按需收口**;强模型替你兜住了大部分(编程简单,但硬件多做了保证)。

## 四、三种成因,三种拦截手段(别用错)

第一节说"重排是多层叠加",这里落到实处:**同一种重排,成因在哪一层,就得用哪一层的屏障拦。用错层完全无效。**

| 成因层 | 什么时候发生 | 现象 | 拦截手段 | 有无 CPU 指令 |
|--------|-------------|------|---------|:---:|
| **编译器重排** | 编译期(静态) | 编译器为优化调换无依赖的读写 | **编译器屏障** `asm volatile("":::"memory")`、`std::atomic_signal_fence` | ❌ 无指令,零运行开销 |
| **流水线乱序执行** | 运行期 | 乱序引擎投机执行 | x86 靠 **machine-clear** 自动拉回 TSO(基本不用管);弱模型靠硬件屏障 | —— |
| **store buffer / invalidate queue** | 运行期 | 写可见延迟 / 失效延迟生效 | **硬件内存屏障** `mfence`/`lfence`/`sfence`、`dmb`/`ldar`/`stlr` | ✅ 真指令,有开销 |

**最容易踩的坑**:以为加了编译器屏障(`asm volatile("":::"memory")`)就万事大吉——它只拦编译器,**拦不住 store buffer 的 StoreLoad**。反过来,`std::atomic` 的 `seq_cst` 会**同时**压住编译器和硬件两头,所以才"贵但安全"。

```plantuml
@startuml
skinparam shadowing false
skinparam rectangle {
  BackgroundColor<<c>> #FFF9C4
  BorderColor<<c>>     #F9A825
  BackgroundColor<<h>> #FFCCBC
  BorderColor<<h>>     #E64A19
  BackgroundColor<<b>> #C8E6C9
  BorderColor<<b>>     #388E3C
}
rectangle "编译器屏障\n(软:asm volatile memory)" <<c>> as C
rectangle "硬件内存屏障\n(硬:mfence/dmb…)" <<h>> as H
rectangle "std::atomic / seq_cst\n= 两者一起下" <<b>> as B
C -down-> B
H -down-> B
note left of C : 只拦编译期重排\n挡不住 store buffer
note right of H : 只拦运行期硬件重排\n挡不住编译器
note bottom of B : 要正确,通常两层都要拦\n这就是 std::atomic 帮你做的事
@enduml
```

## 五、屏障如何逐一封堵四种重排

内存屏障不是"一刀切全禁",而是**按需只禁某几种**——这正是 `release`/`acquire`/`seq_cst` 分档的本质:各自对应"禁哪几种重排"。

| 屏障(概念) | 禁止的重排 | C++ 对应 | 硬件例子 | 典型用途 |
|-----------|-----------|---------|---------|---------|
| **LoadLoad + LoadStore 屏障** | 屏障前的**读**不能被排到屏障后 | `acquire` | ARM `ldar`;x86 普通 load 自带 | 读锁/读标志后,保证后续读到的是新数据 |
| **StoreStore + LoadStore 屏障** | 屏障后的**写**不能被排到屏障前 | `release` | ARM `stlr`;x86 普通 store 自带 | 写完数据再发布标志,保证别人看到标志时数据已就绪 |
| **全屏障(含 StoreLoad)** | **四种全禁**(尤其 StoreLoad)| `seq_cst` | x86 `mfence`/`lock`;ARM `dmb ish` | 需要全局一致顺序(如 Dekker 锁) |

几个要点串起前面的分篇:

- **`acquire`(读屏障) 治的是 LoadLoad/LoadStore**——正是 [invalidate-queue.md](/concepts/memory-ordering/invalidate-queue.md) 里"排空失效队列、别读到旧值"那半;所以 `acquire` 的硬件含义就是排空 invalidate queue。
- **`release`(写屏障) 治的是 StoreStore/LoadStore**——正是 [store-buffer.md](/concepts/memory-ordering/store-buffer.md) 里"让此前的写先出去"那半;所以 `release` 的硬件含义偏向按序排空 store buffer。
- **只有 `seq_cst`/全屏障能治 StoreLoad**——因为 StoreLoad 来自 store buffer 的本质(写没提交、读先跑),必须**强制排空 store buffer**才堵得住,这也是 `mfence`/`lock` 最贵的原因。acquire/release 都各做半边,唯独不碰 StoreLoad,所以便宜。
- **`release`/`acquire` 必须配对**:发布方 `release`(写侧,压 store buffer)、接收方 `acquire`(读侧,排 invalidate queue),两半合起来才封住一次"发布-读取"的完整乱序窗口(见 [invalidate-queue.md](/concepts/memory-ordering/invalidate-queue.md) §4 的 producer/consumer)。

> **一句话**:屏障的分档 = "禁哪几种重排"的分档。acquire 管两种(读打头)、release 管两种(写收尾)、seq_cst 管全部(唯一能收拾 StoreLoad 的)。选最弱够用的那档,就是内存序调优的全部。

## 六、一张全景速查表(收口)

把前面所有维度压成一张表——**遇到"这个重排是什么/谁造成的/x86 会不会/怎么防"直接查这里**:

| 重排 | 含义 | 主因部件 | x86 | ARM | 防它用 |
|------|------|---------|:---:|:---:|--------|
| **LoadLoad** | 两读换序 | invalidate queue + 投机读 | ❌ | ✅ | `acquire` |
| **LoadStore** | 读未完写先行 | 乱序执行 | ❌ | ✅ | `acquire` 或 `release` |
| **StoreStore** | 两写换序 | store buffer 非 FIFO | ❌ | ✅ | `release` |
| **StoreLoad** | 写后读提前 | **store buffer(人人都有)** | ✅ | ✅ | **仅 `seq_cst`/全屏障** |

配套记忆:

- **横看架构**:x86 那列只有 StoreLoad 打 ✅(强模型 TSO);ARM 那列全 ✅(弱模型)。
- **竖看防御**:读打头的(LoadLoad/LoadStore)归 `acquire`,写收尾的(StoreStore/LoadStore)归 `release`,唯独 StoreLoad 谁都收拾不了、只能上全屏障。
- **LoadStore 出现两次**:它既能被 acquire(约束前面的读)也能被 release(约束后面的写)挡住,是唯一"两边都沾"的重排。

## 七、和本仓库其他文档的关系

- **成因分篇**:[store-buffer.md](/concepts/memory-ordering/store-buffer.md)(StoreLoad/StoreStore 的来源——发起侧缓冲)、[invalidate-queue.md](/concepts/memory-ordering/invalidate-queue.md)(LoadLoad/LoadStore 的来源——接收侧缓冲)。本篇是这两篇的**总纲**,建议先读分篇看机制、再回本篇看全景。
- **应用**:[atomic.md](/concepts/cache/atomic.md)(`std::memory_order` 各档、`lock` 自带全屏障)——本篇第五节的屏障分档,就是 atomic 里 relaxed/acquire/release/seq_cst 的重排语义解释。
- **C++ 语义视角**:[memory-order.md](/concepts/memory-ordering/memory-order.md)——从 happens-before / synchronizes-with 模型自顶向下讲六个 `memory_order` 各自承诺什么、配可运行代码。本篇讲硬件为什么、那篇讲语言怎么用,互补。
- **上游硬件**:[msi.md](/concepts/cache/msi.md)/[mesi.md](/concepts/cache/mesi.md)(一致性协议保证"最终一致",而重排讨论的是"中间过程的顺序可见性"——协议管值对不对,内存序管顺序看得对不对,是正交的两件事)。
- **观测**:内存序相关的流水线清空可用 `perf` 看(x86 的 `machine_clears.memory_ordering` 就是投机读违反 TSO 被拉回的计数):

```bash
perf stat -e cycles,instructions,machine_clears.memory_ordering ./app
perf list | grep -i "machine_clears\|mem_"
```

## 八、一句话总结

> **内存重排只有 4 种(LoadLoad/LoadStore/StoreStore/StoreLoad),复杂的不是种类而是"成因 × 架构":每种由不同硬件部件造成(store buffer 管 Store 打头的、invalidate queue 管 Load 打头的),x86-TSO 只放行 StoreLoad、ARM/Power 四种全放行。封堵要对成因下药——编译期用编译器屏障、运行期用硬件屏障;而屏障的 acquire/release/seq_cst 分档,本质就是"禁哪几种重排"的分档,其中只有 seq_cst/全屏障能收拾那个人人都有、最顽固的 StoreLoad。**
