# MESI 缓存一致性协议 —— 状态机、总线事务与伪共享

> 和 [NUMA](/concepts/numa/numa.md) 同属"多核内存子系统"的底层，但层次更低：NUMA 讲**内存离哪颗 CPU 近**，MESI 讲**每颗核的私有缓存里同一份数据怎么保持一致**。不懂它，会遇到"多线程各写各的变量、没加锁、却比单线程还慢"的诡异现象——罪魁是**伪共享(false sharing)**。


> 本篇是缓存一致性协议族的主文档。前身见 [msi.md](/concepts/cache/msi.md)；变种见 [moesi.md](/concepts/cache/moesi.md)(AMD)、[mesif.md](/concepts/cache/mesif.md)(Intel)、[dragon.md](/concepts/cache/dragon.md)(写更新)；横向对比见 [comparison.md](/concepts/cache/comparison.md)。

## 一、问题的根源：多核私有缓存

现代 CPU 每个核有自己的 L1/L2 缓存，内存以 **cache line（缓存行，通常 64 字节）** 为单位在缓存和内存间搬运。同一 cache line 会被多个核各存一份私有副本——一致性问题就出在这里。

> 完整的物理拓扑（L1/L2 私有、L3/内存共享、总线/互联如何连接各核、各级延迟）和"为什么需要一致性"的问题起点，见 [msi.md 第一节](/concepts/cache/msi.md#一先看物理拓扑多核缓存长什么样)——协议就作用在"私有缓存"这一层。本篇直接从"协议怎么做"讲起。

**核心矛盾**：同一块内存被多个核各缓存了一份。核 0 改了自己缓存里的 X，核 1 缓存里还是旧的 X——如果放任不管，两个核看到的内存值就不一致了，程序会算错（这个 bug 的完整演示见 [msi.md 第四节](/concepts/cache/msi.md)）。

**缓存一致性协议**就是硬件用来保证"多个副本对外看起来始终像一份"的规则。MESI 是最经典的一种：给每个 cache line 标一个**状态**，用**总线嗅探(bus snooping)** 让各核互相监听、按状态机迁移。

> 术语：这类协议叫 **write-invalidate（写失效）**——某核要写时，先让别人的副本失效，保证同一时刻只有一个"可写"的副本。对立的一类是 **write-update（写更新）**。两条路线的决策分叉见 [msi.md 第五节](/concepts/cache/msi.md)，写更新详解见 [dragon.md](/concepts/cache/dragon.md)。MESI/MOESI/MESIF 都属于写失效族。

## 二、四种状态（MESI）

每个 cache line 在**每个核的缓存里**独立地处于四态之一：

| 状态 | 全称 | 含义 | 和内存一致? | 其他核可能有副本? | 可否直接写 |
|------|------|------|:---:|:---:|:---:|
| **M** | Modified（已修改）| 本核改过、是最新值，内存里是旧的；**独此一份** | 否（内存脏）| 否 | 可（已拥有）|
| **E** | Exclusive（独占）| 只有本核缓存了它，且**未改**、与内存一致 | 是 | 否 | 可（无需通知别人）|
| **S** | Shared（共享）| 多个核都缓存了，都是干净只读副本 | 是 | 是 | 否（要先让别人失效）|
| **I** | Invalid（无效）| 本核这行失效/没有，不能用 | — | — | — |

三条关键不变式（协议时刻维持）：

1. **M 和 E 是"独占"态**：全系统只有一个核能对某行处于 M 或 E；此时别的核对该行必为 I。
2. **写之前必须独占**：要写一行，得先把它变到 M（若在 S，先让所有其他副本失效）。
3. **M 态的数据比内存新**：别人要读它，必须先从 M 那个核拿到（write-back 或转发），不能直接读内存。

**E 状态存在的意义**（MESI 相比更简单的 [MSI](/concepts/cache/msi.md) 的关键优化）：如果一个核独占且干净（E），它要改这行时**不需要发任何总线消息**就能直接升到 M——因为没人有副本，不用通知谁。省下的这次总线广播，在"读后即写"（如 `counter++`、初始化后修改）的常见模式里非常划算。

## 三、状态机（PlantUML）

沿用 [msi.md 第七节](/concepts/cache/msi.md#七状态机plantuml) 的信号约定——分**处理器侧(Pr)** 和**总线侧(Bus)** 两类：`PrRd`/`PrWr`(本核读/写)、`BusRd`(嗅探到别核读)、`BusRdX`(别核读独占/RFO)、`BusUpgr`(别核把 S 升 M)、`Flush`(把 M 写回或转发)。完整术语表见 [msi.md 第七节](/concepts/cache/msi.md#七状态机plantuml)。

配色：<font color=#C62828>红=M(脏独占)</font>、<font color=#1976D2>蓝=E(干净独占)</font>、<font color=#388E3C>绿=S(共享)</font>、<font color=#757575>灰=I(失效)</font>。

### 3.1 本核处理器操作驱动的迁移

```plantuml
@startuml
skinparam shadowing false
hide empty description
skinparam state {
  BackgroundColor<<m>> #FFCDD2
  BorderColor<<m>>     #C62828
  BackgroundColor<<e>> #BBDEFB
  BorderColor<<e>>     #1976D2
  BackgroundColor<<s>> #C8E6C9
  BorderColor<<s>>     #388E3C
  BackgroundColor<<i>> #EEEEEE
  BorderColor<<i>>     #757575
}
state "Modified"  as Modified  <<m>>
state "Exclusive" as Exclusive <<e>>
state "Shared"    as Shared    <<s>>
state "Invalid"   as Invalid   <<i>>
[*] --> Invalid
Invalid --> Exclusive : PrRd\n(总线上无其他副本)
Invalid --> Shared    : PrRd\n(其他核有副本 → BusRd)
Invalid --> Modified  : PrWr\n(发 BusRdX 抢独占)
Shared --> Modified   : PrWr\n(发 BusUpgr 让其他失效)
Shared --> Shared     : PrRd (命中)
Exclusive --> Modified : PrWr\n(独占，无需总线消息!)
Exclusive --> Exclusive: PrRd (命中)
Modified --> Modified : PrRd / PrWr (命中)
note right of Exclusive
  E→M 不发总线消息，
  是 MESI 对 MSI 的核心优化
end note
@enduml
```

### 3.2 总线嗅探（监听到别人操作）驱动的迁移

每个核同时在**监听总线**，看到别的核对自己缓存的行发起操作时，被动迁移：

```plantuml
@startuml
skinparam shadowing false
hide empty description
skinparam state {
  BackgroundColor<<m>> #FFCDD2
  BorderColor<<m>>     #C62828
  BackgroundColor<<e>> #BBDEFB
  BorderColor<<e>>     #1976D2
  BackgroundColor<<s>> #C8E6C9
  BorderColor<<s>>     #388E3C
  BackgroundColor<<i>> #EEEEEE
  BorderColor<<i>>     #757575
}
state "Modified"  as M <<m>>
state "Exclusive" as E <<e>>
state "Shared"    as S <<s>>
state "Invalid"   as I <<i>>
M --> S : 嗅探到 BusRd\n(先 Flush 把最新值写回/转发)
M --> I : 嗅探到 BusRdX\n(Flush 后失效)
E --> S : 嗅探到 BusRd\n(降级为共享)
E --> I : 嗅探到 BusRdX
S --> I : 嗅探到 BusRdX / BusUpgr
note top of M : 别人来读我改过的行，\n我必须先把最新数据交出去
@enduml
```

一句话概括状态机：**本核读写让自己"往上升"（I→S/E→M）；别人的读写通过总线嗅探把自己"往下压"（M→S→I）。**

## 四、一次写操作触发了什么（总线事务时序）

假设核 A、核 B 都缓存了同一行且都是 **S**，现在**核 A 要写**：

```plantuml
@startuml
skinparam shadowing false
skinparam sequence {
  ParticipantBackgroundColor #E3F2FD
  ParticipantBorderColor     #1976D2
  ArrowColor                 #C62828
  LifeLineBorderColor        #90A4AE
}
participant "核A (S→M)" as A
participant "总线/互联" as BUS
participant "核B (S→I)" as B
A -> BUS : ① BusUpgr（我要写这行，请大家失效）
BUS -> B : ② 收到，将该行置为 Invalid
A -> A  : ③ 本行升为 Modified，完成写入
... 稍后 ...
B -> BUS : ④ PrRd 命中失效行 → BusRd（我要重新读）
BUS -> A : ⑤ 通知 A
A -> BUS : ⑥ Flush：把 M 的最新值写回/转发
BUS -> B : ⑦ B 重新加载该行 → 双方变 Shared
note over A, B : 一次跨核"写后读"= 失效广播 + Flush + 重载\n比命中本地缓存慢一两个数量级
@enduml
```

**代价**：每次跨核的"写-读"往返都要经过总线广播、失效、Flush、重载。这套通信走 CPU 间互联（和 [NUMA](/concepts/numa/numa.md) 同一套 QPI/UPI/Infinity Fabric）。偶发无所谓，**高频发生就是性能灾难**——这正是伪共享的机理。

## 五、伪共享(False Sharing)：MESI 的头号性能坑

### 5.1 什么是伪共享

**两个核各自频繁写"逻辑上毫不相干"的两个变量，但这两个变量恰好落在同一个 cache line 里。** 硬件不认识"变量"，只认 cache line——于是每个核写自己的变量，都会把另一个核的**整行**打成 Invalid，触发第四节那套昂贵的失效+Flush+重载。明明没有共享数据，却像在抢同一把锁，故名"伪"共享。

```plantuml
@startuml
skinparam shadowing false
skinparam rectangle {
  BackgroundColor<<line>> #FFF3E0
  BorderColor<<line>>     #EF6C00
  BackgroundColor<<va>>   #BBDEFB
  BorderColor<<va>>       #1976D2
  BackgroundColor<<vb>>   #F8BBD0
  BorderColor<<vb>>       #C2185B
}
rectangle "同一个 64 字节 cache line" <<line>> {
  rectangle "long a\n(核0 疯狂写)" <<va>> as a
  rectangle "long b\n(核1 疯狂写)" <<vb>> as b
}
note bottom
  核0 写 a → 让核1 的整行失效
  核1 写 b → 让核0 的整行失效
  两核互相“乒乓”把对方打成 Invalid → cache line ping-pong
end note
@enduml
```

```c
// 经典伪共享：两线程各写各的，却在同一 64B 行里
struct {
    long a;   // 线程1 疯狂写 a
    long b;   // 线程2 疯狂写 b —— a、b 相邻，同属一个 cache line
} shared;
```

现象：**多线程反而比单线程慢，CPU 忙但没干有用活，`perf stat` 里 cache-misses 极高、IPC 极低。**

### 5.2 解决：cache line 对齐 / 填充

让两个热点变量分处不同 cache line：

```c
// 方法1：C++ alignas 强制每个变量独占一行
struct alignas(64) PaddedLong { long v; };
PaddedLong a, b;
// 方法2：手动填充
struct {
    long a;
    char pad[64 - sizeof(long)];   // 把 b 挤到下一行
    long b;
} shared;
// 方法3：C++17 标准常量（跨平台，通常 = 64）
#include <new>
alignas(std::hardware_destructive_interference_size) long a;
alignas(std::hardware_destructive_interference_size) long b;
```

对齐后各占一行，两核写彼此不再互相失效，多线程才真正并行加速。反面提醒：不要把"多个线程各自频繁更新的计数器/状态"塞进同一个数组或结构体的相邻位置。

> 这里是从**多核伪共享**角度讲"cache line 隔离"。内存对齐更广的性能影响——单次访存的对齐代价、未对齐撕裂/总线锁、结构体 padding、SIMD 对齐——见 [memory-alignment.md](/concepts/cache/memory-alignment.md)。

## 六、如何观测（数据来源）

MESI 是**硬件行为**，不在 `/proc` 里，靠 CPU 的 PMU 硬件计数器，用 [`perf`](/tools/code/perf.md) 采：

```bash
# 整体缓存未命中与 IPC —— 伪共享会让 cache-misses 高、IPC 低
perf stat -e cycles,instructions,cache-references,cache-misses ./app
# 缓存一致性相关事件（事件名因 CPU 型号而异，先 perf list 查）
perf list | grep -iE 'l1d|llc|hitm|coheren|snoop'
# Intel 上定位伪共享的利器
perf c2c record ./app        # c2c = cache-to-cache
perf c2c report              # 报告直接指出 HITM 高的 cache line + 变量偏移
```

关键指标：

- **`cache-misses` 高 + `IPC`(instructions/cycles) 低** → CPU 大量周期耗在等缓存/内存，是伪共享或访存模式差的信号。
- **`perf c2c`** 专抓伪共享：报告指出**哪个 cache line、哪两个变量、被哪些核争抢**。`HITM`(Hit Modified，跨核命中对方 M 态行) 就是伪共享的指纹——对应第四节 M→S/I 的 Flush。

## 七、和 NUMA 的关系与区别

| | MESI（本篇）| [NUMA](/concepts/numa/numa.md) |
|---|---|---|
| 层次 | **CPU 缓存**（cache line，64B）| **主内存**（哪个节点的内存条）|
| 解决什么 | 多核缓存副本一致性 | 内存离运行的核有多远 |
| 典型坑 | **伪共享**：变量挤在同一 cache line | **远程访问**：数据在别的节点内存 |
| 优化 | cache line 对齐/填充 | 绑定进程/内存到同节点（[numactl](/tools/numa/numactl.md)）|
| 观测 | `perf stat` cache-misses、`perf c2c` | `numastat`、`numactl -H` |

> 多路 NUMA 机器上两者叠加：**跨节点 + 跨核**的缓存失效最贵。优化顺序：先用 NUMA 绑定把数据和线程收拢到一个节点，再用 cache line 对齐消除节点内伪共享。

## 八、结合本仓库

`cpu_demo` 是单线程程序，触发不了伪共享（需多核同时写相邻变量）。要体会的话写个最小对照实验：两线程各写一个 `long`，对比"挤同结构体" vs "各自 64B 对齐"的耗时——后者常快数倍。

```bash
perf stat -e cache-misses,cycles,instructions ./your_mt_app   # 先看有无一致性开销
perf c2c record ./your_mt_app && perf c2c report              # cache-miss 高就用 c2c 定位
```

> ⚠️ 平台提醒：`perf c2c` 和精确的一致性事件需 **Linux + 对应 CPU 的 PMU 支持**（Intel/AMD 事件名不同），常需 root 或调低 `kernel.perf_event_paranoid`；虚拟机里 PMU 可能被屏蔽。

> 相关：前身 [msi.md](/concepts/cache/msi.md)；变种 [moesi.md](/concepts/cache/moesi.md) / [mesif.md](/concepts/cache/mesif.md) / [dragon.md](/concepts/cache/dragon.md)；对比 [comparison.md](/concepts/cache/comparison.md)；采样工具 [../code/perf.md](/tools/code/perf.md)；多路内存亲和 [../numa/numa.md](/concepts/numa/numa.md)。
