原文: https://jia.je/hardware/2024/09/04/memory_model_and_memory_ordering/

决定访存的是否乱序,如何乱序,需要考虑:

简单的内存模型 —— SC 模型

如果CPU 不做任何的乱序 -> program order == memory order

不同核心的访存序列到达内存子系统,同一个核心内的访存序列保持 program order。由于不同核心发起访存的时间不同,最后在内存子系统上执行的访存可能有这些情况:

Store Buffer

Store Buffer 是乱序执行 CPU 一个非常重要的优化,因为有些指令是有副作用的,不能够仅仅通过分支预测就让它执行,因此 CPU 对某些指令要求一定要做一次同步(即保证该在该指令之前的指令都已经完成了执行),那么这个指令就会一直阻塞,而若此时发生了缓存缺失,则会使得阻塞更加恶化。

为了解决这个问题,通常认为 Store 指令是不会失败的,因此可以放入一个队列里面,这个队列就称为 Store BufferStore Buffer 会保证指令按照顺序异步写入缓存,并且读需要同时读 Store Buffer

X86-TSO

依托 Store Buffer,我们可以构建出一个新的内存模型:在每个核心和内存子系统之间,多了一个 Store Buffer,Store 指令会先进入 Store Buffer,再进入内存子系统。当 Load 指令和 Store Buffer 中的 Store 指令有数据相关时,会从 Store Buffer 中取数据,如果不相关,或者不完全相关(例如只有一部分重合),则会从内存子系统中取数据,此时从内存子系统的角度来看,就发生了 Load 提前于 Store 执行的重排。这个模型被称为 X86-TSO

QQ_1726473977983.png

这里的 TSO 的全称是 Total Store Order,意思是针对 Store 指令(只有离开 Store Buffer 进入缓存的才算),有一个全局的顺序。内存子系统会处理来自不同核心的 Store,但会保证 Store 有一个先后顺序,并且所有核心会看到同一个顺序。

Weak/Relaxed Memory Model

在一些 X86 以外的指令集架构中(例如 ARM v8),有另外一种内存模型,一遍称为 Weak/Relaxed Memory Model。全都允许重排。如果用户不想重排,那再加合适的 fence 或 barrier 指令,阻止不想要的重排。在这个内存模型下,每个核心可以在向内存子系统读写前,对自己的读写进行重排

指令集会提供一些 fence 或者 barrier 指令来阻止各种类型的重排

不同的处理器和指令集使用了不同的内存模型,提供了不同的指令来控制乱序重排,但是对于软件开发者来说,会希望尽量用一套通用的 API 来控制乱序重排,可以兼容各种指令集,不用去记忆每个处理器用的是什么内存模型,不用去知道哪些指令可以用来解决哪些重排。

这个 API 在很多编程语言中都有,C 的 stdatomic.h,C++ 的 std::memory_order,Rust 的 std::sync::atomic::Ordering 等等。它们对各种处理器的内存序进行了进一步的抽象,并且在编译的时候,由编译器或标准库把这些抽象的内存序翻译成实际的指令。