最简单的答案:您必须使用 3 个栅栏之一(LFENCE、SFENCE、MFENCE)来提供 6 个数据一致性之一:
C++11:
最初,您应该从内存访问顺序的角度考虑这个问题,这在 C++11 中有很好的文档和标准化。你应该先阅读:http://en.cppreference.com/w/cpp/atomic/memory_order
x86/x86_64:
1. Acquire-Release Consistency: 那么,重要的是要明白在x86中访问常规RAM(默认标记为WB-Write Back,和WT相同的效果(Write通过使用 asm MOV 而不使用任何其他命令自动提供获取-释放一致性的内存顺序 - std::memory_order_acq_rel。
IE。因为这个内存只使用std::memory_order_seq_cst 来提供顺序一致性是有意义的。即当您使用:std::memory_order_relaxed 或std::memory_order_acq_rel 时,std::atomic::store()(或std::atomic::load())的编译汇编代码将是相同的——只有MOV 没有任何L/S/MFENCE。
注意: 但是你必须知道,不仅 CPU 和 C++ 编译器都可以使用内存重新排序操作,并且所有 6 个内存屏障总是影响 C++ 编译器,无论 CPU 架构如何。
那么,你必须知道,如何将它从 C++ 编译为 ASM(本机机器码),或者如何在汇编器上编写它。要提供任何一致性排除顺序,您可以简单地编写MOV,例如MOV reg, [addr] 和MOV [addr], reg 等。
2。顺序一致性:但要提供顺序一致性,您必须使用隐式 (LOCK) 或显式围栏 (L/S/MFENCE),如下所述:Why GCC does not use LOAD(without fence) and STORE+SFENCE for Sequential Consistency?
-
LOAD(无围栏)和STORE + MFENCE
-
LOAD(无围栏)和LOCK XCHG
-
MFENCE + LOAD 和 STORE(没有围栏)
-
LOCK XADD ( 0 ) 和 STORE (没有围栏)
比如GCC用1,而MSVC用2。(但你要知道,MSVS2012有一个bug:Does the semantics of `std::memory_order_acquire` requires processor instructions on x86/x86_64?)
然后,您可以阅读 Herb Sutter,您的链接:https://onedrive.live.com/view.aspx?resid=4E86B0CF20EF15AD!24884&app=WordPdf&authkey=!AMtj_EflYn2507c
规则例外:
此规则适用于通过使用MOV 访问默认标记为 WB - 回写的常规 RAM。内存标记在Page Table,在每个PTE(Page Table Enrty)中为每个Page(4 KB 连续内存)。
但也有一些例外:
如果我们将页表中的内存标记为写入组合(POSIX 中为ioremap_wc()),则自动只提供 Acquire Consistency,我们必须按照以下段落操作。
查看我的问题的答案:https://*.com/a/27302931/1558037
- 对内存的写入不会与其他写入一起重新排序,以下例外情况:
- 使用 CLFLUSH 指令执行写入;
- 使用非临时移动指令(MOVNTI、MOVNTQ、MOVNTDQ、MOVNTPS 和 MOVNTPD)执行的流式存储(写入);和
- 字符串操作(参见第 8.2.4.1 节)。
在两种情况 1 和 2 中,即使您想要获取-发布一致性,您也必须在两次写入同一地址之间使用额外的 SFENCE,因为这里自动提供仅获取一致性,您必须自己执行发布 (SFENCE) .
回答您的两个问题:
有时在进行存储时,CPU 会写入其存储缓冲区
而不是 L1 缓存。但是我不明白关于
哪个 CPU 会这样做?
从用户的角度来看,缓存 L1 和 Store Buffer 的作用不同。 L1 快,但 Store-Buffer 更快。
Store-Buffer - 是一个简单的队列,其中仅存储写入,并且不能重新排序 - 它用于提高性能和隐藏访问缓存的延迟(L1 - 1ns、L2 - 3ns、L3 - 10ns)(CPU-Core认为Write已经存入缓存并执行下一条命令,但同时你的Writes只存入Store-Buffer,稍后会存入缓存L1/2/3),即CPU - 当写入被存储到缓存时,核心不需要等待。
缓存 L1/2/3 - 看起来像透明关联数组(地址 - 值)。它很快但不是最快的,因为 x86 通过使用cache coherent 协议MESIF/MOESI 自动提供Acquire-Release Consistency。这样做是为了更简单的多线程编程,但会降低性能。 (确实,我们可以使用无写入争用算法和数据结构,而无需使用缓存一致性,即没有 MESIF/MOESI,例如 PCI Express)。协议 MESIF/MOESI 在QPI 上工作,它连接 CPU 中的核心和多处理器系统中不同 CPU 之间的核心 (ccNUMA)。
CPU2 可能希望加载一个已写入 CPU1 的值
存储缓冲区。据我了解,问题是 CPU2 看不到
CPU1 的存储缓冲区中的新值。
是的。
为什么 MESI 协议不能只是
包括刷新存储缓冲区作为其协议的一部分??
MESI 协议不能只将刷新存储缓冲区作为其协议的一部分,因为:
- MESI/MOESI/MESIF protoclos 与 Store-Buffer 无关,也不知道。
- 在每次写入时自动刷新存储缓冲区会降低性能 - 并使其无用。
- 使用某些命令手动刷新所有远程 CPU 核心上的存储缓冲区(我们不知道哪个核心存储缓冲区包含所需的写入)- 会降低性能(在 8 个 CPU x 15 个核心 = 120 个核心)时间刷新存储缓冲区 - 这太糟糕了)
但是手动刷新当前 CPU-Core 上的存储缓冲区 - 是的,您可以通过执行 SFENCE 命令来完成。您可以在两种情况下使用SFENCE:
- 通过可写回缓存在 RAM 上提供顺序一致性
- 在规则的例外情况上提供获取-释放一致性:具有可缓存写入组合的 RAM,用于使用 CLFLUSH 指令执行的写入以及用于非临时 SSE/AVX 命令
注意:
在 x86/x86_64 上是否需要 LFENCE? - 问题并不总是很清楚:Does it make any sense instruction LFENCE in processors x86/x86_64?
其他平台:
然后,您可以在理论上使用 Store-Buffer 和 Invalidate-Queue 阅读(对于真空中的球形处理器),您的链接:http://www.puppetmastertrading.com/images/hwViewForSwHackers.pdf
以及如何在其他平台上提供顺序一致性,不仅使用 L/S/MFENCE 和 LOCK,还使用 LL/SC:http://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html