最简单的答案:您必须使用 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 Throught)或UC(Uncacheable)效果相同)由MOV
在没有任何附加命令的情况下使用 asm会自动为 Acquire-Release Consistency -提供内存顺序std::memory_order_acq_rel
。即,此内存std::memory_order_seq_cst
仅用于提供顺序一致性才有意义。即,当您使用时:std::memory_order_relaxed
或者(or )std::memory_order_acq_rel
的编译汇编代码将是相同的 - 只是没有任何.std::atomic::store()
std::atomic::load()
MOV
L/S/MFENCE
注意:但您必须知道,不仅 CPU 和 C++ 编译器都可以使用内存重新排序操作,并且无论 CPU 架构如何,所有 6 个内存屏障都会影响 C++ 编译器。
那么,你必须知道,如何将它从 C++ 编译为 ASM(本机机器码),或者如何在汇编器上编写它。要提供任何 Consistency exclude Sequential 您可以简单地编写MOV
,例如MOV reg, [addr]
等MOV [addr], reg
。
2. 顺序一致性:但是要提供顺序一致性,您必须使用隐式LOCK
(MFENCE
LOAD
(不带围栏)和STORE
+MFENCE
LOAD
(没有围栏)和LOCK XCHG
MFENCE
+LOAD
和STORE
(没有围栏)
LOCK XADD
( 0 ) 和STORE
(没有围栏)
例如,GCC 使用 1,而 MSVC 使用 2。(但你必须知道,MSVS2012 有一个 bug:`std::memory_order_acquire` 的语义是否需要 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 连续内存)。
但也有一些例外:
如果我们将页表中的内存标记为写入组合(ioremap_wc()
在 POSIX 中),则自动仅提供 Acquire Consistency,我们必须按照以下段落操作。
请参阅我的问题的答案:https ://stackoverflow.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-Core 不需要等待 Writes 何时被存储到缓存中。
缓存 L1/2/3 - 看起来像透明关联数组(地址 - 值)。它很快但不是最快的,因为 x86 通过使用缓存一致性协议MESIF / MOESI自动提供 Acquire-Release Consistency 。这样做是为了更简单的多线程编程,但会降低性能。(确实,我们可以使用无写入争用算法和数据结构,而无需使用缓存一致性,即无需 MESIF/MOESI,例如通过PCI Express)。协议 MESIF/MOESI 通过QPI工作,它连接 CPU 中的核心和多处理器系统 ( ccNUMA )中不同 CPU 之间的核心。
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 命令
笔记:
我们LFENCE
在任何情况下都需要 x86/x86_64 吗?- 问题并不总是很清楚:处理器 x86/x86_64 中的指令 LFENCE 是否有意义?
其他平台:
然后,您可以在理论上使用 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