什么是内存一致性模型
内存一致性模型决定了不同的线程对共享内存的访问的可见性,也就是说,当不同的线程写同一块内存时, 读内存会返回什么值.
考虑下面的例子:
初始状态: x = y = 0;
Processor 0:
(1) x = 1;
(2) print (y);
Processor 1:
(3) y = 1;
(4) print(x);
假设在如下的CPU架构上执行,并且硬件上保证了cache一致性.
那么上述代码最终会打印出什么值呢?
这依赖于所用的内存一致性模型
顺序一致性模型SC (Sequential Consistency)
CPU严格按照程序中的顺序执行,上述例子有如下几种执行顺序:
(1)->(2)->(3)->(4): 输出 0 1;
(3)->(4)->(1)->(2): 输出 0 1;
(1)->(3)->(2)->(4): 输出 1 1;
(1)->(3)->(4)->(2): 输出 1 1;
....
等等, 最终结果可以是 0 1或者 1 1, 但不能为 0 0
在顺序一致性模型里, 在前一条指令完成前, 不会执行后一条指令, (1)是一条写操作,由于缓存架构(L1/L2/L3),速度是相当于的慢.
宽松内存模型(Relaxed memory models)
对于(2)而言,为什么一定要等(1)执行完才能执行呢?它们并没有什么直接的联系, 因此在硬件上设计了一个store buffer, 如下图:
(1)执行时直接写入store buffer后, (2)就可以紧接着可以执行,如果store buffer以FIFO的方式运行, 这种模型称为total store ordering (TSO)
Total store ordering (TSO)
回到前面的例子,
(1)和(3)已经执行完, 将值写入到store buffer, 紧接着(2)和(4)执行, 这时(2)执行, 它首先到store buffer中查找是否有B的值, 但发现没有, 所以从内存中读取B的值,得到0, 同样(4)也有可能会获取到0, 这样程序的输出结果就是 0 0, 这在SC模型中是不可能的值.
Partial Consistency Ordering(PSO)
TSO保证CPU以FIFO的方式来处理store buffer, 但如果store buffer中的指令涉及不同的地址,允许CPU重排它们的顺序以便获得性能的提升,这就是
Partial Consistency Ordering, PSO仅保证地址相关指令在顺序.
内存屏障
在宽松内存一致性模型下, 程序可能出现与预期不一致的结果, 比如例子中输出 0 0,因此芯片设计厂商提供了同步指令用来控制指令的运行顺序,常用的指令为内存屏障barrier(or fence), 内存屏障强制保证在它之前的所有的内存操作指令必须完成后才能执行之后的指令.也就是内存屏障指令保证了该点的顺序一致性.
通常内存屏障指令分为读内存屏障,写内存屏障和读写内存屏障, 例如arm中:
- LD load-load/load-store
- ST store-store/store-load
- SY any-any
编译乱序
前面的乱序行为我们称为执行乱序, 事实上编译器也会进行指令重排从而导致乱序.编译器可以对访存的指令进行乱序,减少逻辑上不必要的访存,以及尽量提高 Cache 命中率和 CPU 的 Load/Store 单元的工作效率。因此在打开编译器优化以后,有时会看到生成的汇编码并没有严格按照代码的逻辑顺序。因此同样需要内存屏障指令以保证程序执行的正确性.