正如我们从之前对Does it make any sense instruction LFENCE in processors x86/x86_64?的回答中所知道的,我们不能使用SFENCE
代替MFENCE
来实现顺序一致性。
这里的答案是MFENCE
= SFENCE
+ LFENCE
,即LFENCE
做了一些事情,没有这些事情我们就不能提供顺序一致性。LFENCE
无法重新排序:
SFENCE
LFENCE
MOV reg, [addr]
--到-->
MOV reg, [addr]
SFENCE
LFENCE
例如,mechanism - Store Buffer提供的MOV [addr], reg
LFENCE
--> LFENCE
MOV [addr], reg
的重新排序,该机制重新排序Store - Loads以提高性能,因为LFENCE
不会阻止它。SFENCE
禁用此机制。
什么机制禁用了LFENCE
,使其无法重新排序(x86没有机制- Invalidate-Queue)?SFENCE
MOV reg, [addr]
--> MOV reg, [addr]
SFENCE
的重新排序是否仅在理论上或现实中可能?如果可能的话,在现实中,什么机制,它是如何工作的?
3条答案
按热度按时间6qftjkof1#
x86围栏指令可以简要描述如下:
cpuid
或新的serialize
,然后跳转到另一个线程在释放标志之前存储代码的缓冲区。代码提取保证获得新指令。与数据加载不同,代码提取不遵守x86通常的LoadLoad排序规则。这些描述在确切地排序什么类型的操作方面有点模糊,并且在供应商之间存在一些差异(例如SFENCE在AMD上更强),甚至在同一供应商的处理器上也更强。有关详细信息,请参阅英特尔手册和规格更新以及AMD手册和修订指南。在SO其他其他地方也有很多关于这些指示的其他讨论。首先阅读官方来源。我认为上面的描述是供应商之间的最低书面行为。
脚注1:后续存储的OoO exec 不需要被MFENCE阻止;执行它们只是将数据写入存储缓冲区。按顺序提交已经在较早的存储之后对它们进行排序,并且在引退命令wrt之后提交。加载(因为x86要求加载在退出之前完成,而不仅仅是开始,作为确保加载顺序的一部分)。请记住,x86硬件不允许除StoreLoad之外的其他重新排序。
英特尔手册第2卷编号325383- 072 US将SFENCE描述为“确保SFENCE之前的每个存储在SFENCE之后的任何存储变得全局可见之前全局可见”的指令。第3卷第11.10节指出,当使用SFENCE时,存储缓冲区被耗尽。对这句话的正确解释正是第二卷中较早的那句话。因此,可以说SFENCE在这个意义上耗尽了存储缓冲区。在SFENCE的生命周期中,无法保证早期商店在什么时候实现GO。对于任何较早的商店,它可能发生在SFENCE退役之前、之时或之后。关于GO的意义是什么,这取决于几个因素。这超出了问题的范围。参见:Why “movnti” followed by an “sfence” guarantees persistent ordering?。
MFENCE * 确实 * 必须防止NT存储与其他存储重新排序,因此它必须包括SFENCE所做的任何事情,以及耗尽存储缓冲区。还有从WC内存中重新排序弱序SSE4.1 NT加载,这更难,因为免费获得加载排序的正常规则不再适用于这些。确保此is why a Skylake microcode update strengthened (and slowed) MFENCE也像LFENCE一样消耗ROB。MFENCE仍然有可能比具有HW支持的MFENCE更轻,用于 * 可选地 * 强制执行管道中NT加载的排序。
SFENCE + LFENCE不等于MFENCE的主要原因是因为SFENCE + LFENCE不阻止StoreLoad重新排序,因此不足以实现顺序一致性。只有
mfence
(或lock
艾德操作,或像cpuid
这样的真实的序列化指令)才能做到这一点。请参阅Jeff Preshing的Memory Reordering Caught in the Act,了解只有完整屏障才足够的情况。Intel's instruction-set reference manual entry for
sfence
:处理器确保在SFENCE之后的任何存储变得全局可见之前,SFENCE之前的每个存储全局可见。
但是
它不是根据内存加载或LFENCE指令排序的。
LFENCE强制早期指令“本地完成”(即从内核的无序部分退出),但是对于存储或SFENCE,这仅仅意味着将数据或标记放入存储器顺序缓冲区中,而不是刷新它,以便存储变得全局可见。即,SFENCE“完成”(从ROB引退)不包括刷新存储缓冲区。
这就像Memory Barriers Are Like Source Control Operations中描述的Preshing一样,其中StoreStore屏障不是“即时”的。在那篇文章的后面,他解释了为什么#StoreStore + #LoadLoad +#LoadStore barrier加起来不等于#StoreLoad barrier。(x86 LFENCE具有指令流的一些额外序列化,但由于它不刷新存储缓冲区,因此推理仍然成立)。
LFENCE没有像
cpuid
那样完全序列化(which is as strong a memory barrier asmfence
or alock
ed instruction)。它只是LoadLoad + LoadStore屏障,加上一些执行序列化的东西,这些东西可能开始是一个实现细节,但现在被奉为一个保证,至少在Intel CPU上是这样。它对rdtsc
很有用,并且可以避免分支推测以减轻Spectre。顺便说一句,SFENCE是WB(普通)商店的无操作。
它针对任何存储器而不是针对加载或LFENCE来命令WC存储器(例如移动或存储到视频RAM)。只有在通常是弱有序的CPU上,存储-存储屏障才能对正常存储做任何事情。除非使用NT存储或MapWC的内存区域,否则不需要SFENCE。如果它确实保证在退役之前耗尽存储缓冲区,那么您可以使用SFENCE+LFENCE构建MFENCE,但英特尔的情况并非如此。
真实的的问题是StoreLoad在store和load之间的重新排序,而不是在store和barrier之间的重新排序,所以您应该查看一个store,然后是barrier,然后是load的案例。
可以成为 * 全局可见 *(即提交到L1 d缓存):
1hdlvixo2#
在一般的仁慈!= SFENCE + LFENCE例如,下面的代码,当用
-DBROKEN
编译时,在一些韦斯特米尔和桑迪Bridge系统上失败,但在Ryzen上似乎可以工作。事实上,在AMD系统上,仅仅一个SFENCE似乎就足够了。2nbm6dog3#
什么机制禁用LFENCE,使其无法重新排序(x86没有机制-无效队列)?
从英特尔手册第2A卷第3-464页的
LFENCE
指令文档中:直到所有先前的指令都在本地完成之后,才执行LFENCE,并且直到LFENCE完成之后,才开始执行后续指令
所以,是的,
LFENCE
指令明确阻止了示例中的重新排序。您的第二个示例只涉及SFENCE
指令,这是一个有效的重新排序,因为SFENCE
对加载操作没有影响。