A09 — Memory Barriers and Cache Coherence: Out-of-Order Access on Multi-Core
Key Insight: In a multi-core CPU, the memory order you observe is not necessarily the true order.
Out-of-Order Memory Access on Multi-Core CPUs
Modern CPUs reorder memory access instructions for performance optimization. In multi-core programs, this reordering can cause serious problems.
内存访问乱序的例子:
线程 A:
data = 42; // Store A
flag = true; // Store B
线程 B:
if (flag) { // Load B
print(data); // Load A
}
期望:flag=true 时,data 应该是 42
但 CPU 乱序后可能:flag=true 时,data 还是旧值!
原因:CPU 为了减少 stall,会重排指令CPU 的内存重排类型(x86/x64):
1. 编译器重排:
- 编译器优化代码顺序
- 取决于编译器和优化级别
2. CPU 运行时重排:
- Store Buffer(写缓冲)
- Invalidate Queue(失效队列)
- 流水线并行
x86 的内存模型(Total Store Order):
- 所有核看到 Store 的顺序相同(TSO)
- 但 Store 可能被缓冲,导致其他核看到"先读后写"
- Store Buffer 的存在导致弱保证
ARM/RISC-V 的内存模型(Relaxed):
- 更多的重排可能
- 更容易出现内存顺序问题Memory Barrier
A memory barrier (Memory Fence) tells the CPU: all memory accesses before this must complete before this.
内存屏障的作用:
1. 写屏障(Store Barrier / SFence):
- 确保所有之前的写操作对其他核可见
- 刷新 Store Buffer 到 L1 Cache
2. 读屏障(Load Barrier / LFence):
- 确保所有之前的读操作从全局内存读取
- 清空 Invalidate Queue
3. 全屏障(Full Barrier):
- SFence + LFence
- 所有内存操作必须完成
代码示例(无屏障):
flag = true; // Store
data = 42; // Store
// 可能:data=42 在 flag=true 之前对其他核可见
代码示例(有屏障):
data = 42; // Store
asm volatile ("sfence" ::: "memory"); // 写屏障
flag = true; // Store
// flag=true 时,data=42 一定对其他核可见Linux 内核的内存屏障接口:
#include <linux/barrier.h>
mb(); // 全屏障(读写)
smp_mb(); // 多核全屏障(SMP)
smp_wmb(); // 多核写屏障
smp_rmb(); // 多核读屏障
dma_rmb(); // DMA 读屏障
dma_wmb(); // DMA 写屏障
用户态 C11/C++11:
#include <stdatomic.h>
atomic_thread_fence(memory_order_seq_cst); // 全屏障
atomic_thread_fence(memory_order_release); // 写屏障
atomic_thread_fence(memory_order_acquire); // 读屏障Cache Coherence (MESI Protocol)
Each core of a multi-core CPU has its own L1/L2 Cache. Main memory is shared. The MESI protocol ensures all cores see consistent memory data.
MESI 协议的状态:
M(Modified):脏行,本核独占修改,其他核无效
E(Exclusive):干净行,本核独占,无修改
S(Shared):干净行,多核共享,内容相同
I(Invalid):无效行,本核没有这行数据
状态转换:
1. 读 Modified 行(另一个核请求):
- 本核先写回主存
- 状态 → Shared
2. 写 Shared 行(另一个核也有):
- 先发 Invalidate 给其他核
- 其他核置 Invalid
- 本核状态 → Modified
3. 写 Exclusive 行:
- 直接修改,状态 → Modified
- 不需要通知其他核(只有本核有)
MESI 的开销:
- Invalidate 消息需要总线带宽
- 写竞争严重时,性能下降
- 需要内存屏障来保证顺序Store Buffer 和 Invalidate Queue:
Store Buffer(写缓冲):
- 本核的写先放入 Store Buffer
- 不立即写 L1/L2 Cache
- 其他核可能暂时看不到
- 解决:写屏障刷新 Store Buffer
Invalidate Queue(失效队列):
- 其他核的 Invalidate 消息先放入队列
- 不立即处理
- 导致读到的可能是旧数据
- 解决:读屏障清空 Invalidate Queue
这就是为什么内存屏障在多核编程中如此重要:
- 屏障保证 Store Buffer 刷新
- 屏障保证 Invalidate Queue 处理完毕The Role and Limitations of volatile
volatile 的作用:
volatile int flag;
flag = 1; // 不会被编译器优化掉
volatile 的局限:
- 防止编译器优化(读到缓存而不是内存)
- 不保证 CPU 的运行时重排
- 不保证多核之间的可见性
- 不保证原子性
Linux 内核的做法:
volatile int flag; // 单核有用,多核不够
// 多核需要:
{ smp_mb(); } // 内存屏障
或者用原子操作:
#include <linux/atomic.h>
atomic_t flag;
atomic_set(&flag, 1); // 原子写,有屏障语义C11/C++11 原子操作替代 volatile:
#include <stdatomic.h>
atomic_int flag = ATOMIC_INIT(0);
// 写(Release):
atomic_store(&flag, 1); // 有 release 语义
// 或
flag.store(1, std::memory_order_release);
// 读(Acquire):
int x = atomic_load(&flag); // 有 acquire 语义
// 或
int x = flag.load(std::memory_order_acquire);
memory_order_seq_cst:全屏障(最严格)
memory_order_release:写屏障
memory_order_acquire:读屏障Summary
- Memory Reordering: CPU reorders memory accesses for performance; can cause errors in multi-core scenarios
- Memory Barriers: sfence (write barrier) / lfence (read barrier) / mfence (full barrier); ensure ordering
- MESI: Modified/Exclusive/Shared/Invalid; four states ensuring multi-core cache coherence
- Store Buffer: Writes are buffered; requires sfence to flush. Invalidate Queue: requires lfence to drain
- volatile: Only prevents compiler optimization; does not guarantee CPU reordering or multi-core visibility
- Correct Approach: Use C11/C++11 atomic operations or Linux memory barrier API
Next (A10): How do user programs analyze and optimize memory usage? malloc_stats, mallinfo, valgrind massif, and pmap.
Comments