最简单的答案:您必须使用三个屏障中的一个(
LFENCE
,
SFENCE
,
MFENCE
)来提供六种数据一致性之一:
- 松散的
- 消费的
- 获取的
- 释放的
- 获取-释放的
- 顺序的
C++11:
最初,您应该从内存访问顺序的角度考虑这个问题,这在C++11中得到了很好的记录和标准化。您应该首先阅读:http://en.cppreference.com/w/cpp/atomic/memory_order
x86/x86_64:
1. 获取-释放一致性: 其次,需要了解在
x86中通过使用asm
MOV
访问传统RAM(默认标记为WB - 写回,并且与WT(写入缓存)或UC(不可缓存)具有相同的效果)
自动提供获取-释放一致性的内存顺序 -
std::memory_order_acq_rel
。即,对于此内存,仅使用
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
),如此处所述:
为什么GCC不使用LOAD(无fence)和STORE + SFENCE来实现顺序一致性?
LOAD
(无障碍)和 STORE
+ MFENCE
LOAD
(无障碍)和 LOCK XCHG
MFENCE
+ LOAD
和 STORE
(无障碍)
LOCK XADD
(0)和 STORE
(无障碍)
例如,GCC使用1,但MSVC使用2。(但你必须知道,MSVS2012有一个错误:`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-Write Back的传统RAM的访问,此规则是正确的。在每个PTE(页表项)中的Page Table中,内存被标记为每个页面(4 KB连续内存)。
但也有一些例外情况:
如果我们在页面表中将内存标记为写结合(在POSIX中使用ioremap_wc()),那么自动提供的只有获取一致性,我们必须按照以下段落中所述进行操作。
请参见我的问题答案:https://dev59.com/4mIk5IYBdhLWcg3wp_iM#27302931
- 与其他写入的内存写入不会被重新排序,但有以下几种例外情况:
- 使用CLFLUSH指令执行的写入;
- 使用非临时移动指令(MOVNTI、MOVNTQ、MOVNTDQ、MOVNTPS和MOVNTPD)执行的流式存储(写入);
- 字符串操作(请参见第8.2.4.1节)。
在这两种情况下,即使您想要获取发布一致性,您也必须在两次写入相同地址之间使用额外的SFENCE
,因为这里自动提供的只有获取一致性,您必须自己进行发布操作(SFENCE
)。
回答你的两个问题:
有时在进行存储操作时,CPU 会将数据写入其存储缓冲区而不是 L1 缓存。但我不明白 CPU 会在什么情况下这样做?
从用户的角度来看,缓存 L1 和存储缓冲区的作用是不同的。L1 更快,但存储缓冲区更快。
Store-Buffer - 是一个简单的队列,只存储写操作,并且不能被重新排序 - 它是为了提高性能和隐藏对缓存的访问延迟(L1 - 1ns,L2 - 3ns,L3 - 10ns)而设计的(CPU核心认为写操作已经存储到缓存中并执行下一条指令,但与此同时,您的写操作仅保存在Store-Buffer中,稍后将保存到缓存L1/2/3中),即CPU核心无需等待写操作存储到缓存中。
Cache L1/2/3 - 看起来像透明的关联数组(地址-值)。它很快但不是最快的,因为x86自动使用cache coherent协议MESIF/MOESI提供获取-释放一致性。这是为了更简单地进行多线程编程,但会降低性能。(实际上,我们可以使用无需使用缓存一致性的写争用自由算法和数据结构,例如通过PCI Express)。协议MESIF/MOESI通过连接CPU内核和多处理器系统中不同CPU之间的Cores的QPI工作(ccNUMA)。
CPU2希望加载已写入CPU1存储缓冲区的值。据我所知,问题在于CPU2无法查看CPU1存储缓冲区中的新值。
是的。
为什么MESI协议不能将刷新存储缓冲区作为其协议的一部分?
MESI协议不能将刷新存储缓冲区作为其协议的一部分,因为:
- MESI/MOESI/MESIF协议与存储缓冲区无关,也不了解它。
- 在每次写操作时自动刷新存储缓冲区会降低性能,并使其无用。
- 使用某些命令手动刷新所有远程CPU核心(我们不知道存储缓冲区包含所需写入的哪个核心),会降低性能(在8个CPU x 15个核心=120个核心同时刷新存储缓冲区——这太可怕了)。
但是,在当前CPU核心上手动刷新存储缓冲区是可以的,可以通过执行SFENCE命令来完成。您可以在以下两种情况下使用SFENCE:
- 提供带有写回缓存的RAM的顺序一致性
- 对于“规则异常”的情况:提供使用CLFLUSH指令和非暂态SSE/AVX命令执行的带有写组合缓存的RAM的获取-释放一致性
注意:
在x86/x86_64处理器上是否需要LFENCE
?这个问题并不总是清晰的:在处理器x86/x86_64上使用指令LFENCE是否有意义?
其他平台:
因此,您可以将其视为理论上的情况(在真空中的球形处理器),具有存储缓冲区和无效队列,您的链接:http://www.puppetmastertrading.com/images/hwViewForSwHackers.pdf
如何在其他平台上提供顺序一致性,不仅可以使用L/S/MFENCE和LOCK,还可以使用LL/SC: http://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html