17.4 强弱内存模型
编译器在编译时可能对程序进行内存重排(类似之前展示过的),处理器在微代码级别也会进行内存重排。一般情况下这两种内存重排都是会发生的,两种我们都会进行研究。
我们可以以统一的方式对其进行分类。
内存模型会告诉我们 load 和 store 指令会以哪种形式重排。这里我们不关心访问访问内存的具体指令(mov, movq, etc),只关心对内存到底是读操作,还是写操作。
存在两个极端:弱内存模型和强内存模型。就像弱类型和强类型一样,很多实际存在的约定都会是两种类型的折衷,但相对来说,会较为接近其中的一种。我们找到了 Jeff Preshing [31] 提出的一种分类方法很有用,本书中会一直使用这种方法。
根据该分类方法,内存模型可以被划分为四大类,从较宽松到最强约束。
1.完全弱一致。
这种模型下,可能发生任何形式的内存重排(只要单线程程序的可观测行为不发生变化)。
2.数据依赖顺序弱一致(例如 ARM v7 的硬件内存模型)。
这里我们讲一个数据依赖的特殊类型:在 load 之间的依赖。比如我们从内存中取一个地址的值,然后用这个值再进行一次取值,例如:
Mov rdx, [rbx]
mov rax, [rdx]
在 C 语言里,这是我们使用 -> 符号,通过指向结构体的指针来获取结构体字段的场景。
完全弱一致不保证数据依赖的排序。
3.通常强一致(例如 Intel64 的硬件内存模型)。
这种类型保证所有 store 操作和程序提供的顺序是一致的。但一些 load 操作,则可能会被移动到别处。
Intel 64 位一般都是这种类型的。
4.线性一致
这种类似于你在一个单核心处理器上一步一步调试完全没有优化过的代码,不会发生任何内存重排。