在 x86/x86_64 处理器上使用 LFENCE 指令是不是有意义?
Posted
技术标签:
【中文标题】在 x86/x86_64 处理器上使用 LFENCE 指令是不是有意义?【英文标题】:Does it make any sense to use the LFENCE instruction on x86/x86_64 processors?在 x86/x86_64 处理器上使用 LFENCE 指令是否有意义? 【发布时间】:2013-12-17 10:37:20 【问题描述】:我经常在互联网上发现 LFENCE
在 x86 处理器中毫无意义,即它什么也不做,所以我们可以完全轻松地使用 SFENCE
,因为 MFENCE
= SFENCE
+ @987654328 @ = SFENCE
+ NOP
= SFENCE
.
但如果LFENCE
没有意义,那么为什么我们有四种方法可以在 x86/x86_64 中实现顺序一致性:
LOAD
(无围栏)和STORE
+ MFENCE
LOAD
(无围栏)和LOCK XCHG
MFENCE
+ LOAD
和 STORE
(无围栏)
LOCK XADD
( 0 ) 和 STORE
(没有围栏)
取自这里:http://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html
以及底部第 34 页 Herb Sutter 的表演:https://skydrive.live.com/view.aspx?resid=4E86B0CF20EF15AD!24884&app=WordPdf&wdo=2&authkey=!AMtj_EflYn2507c
如果LFENCE
没有做任何事情,那么方法(3)的含义如下:SFENCE + LOAD and STORE (without fence)
,但是在LOAD
之前做SFENCE
是没有意义的。即如果LFENCE
什么都不做,则方法(3)没有意义。
处理器 x86/x86_64 中的指令 LFENCE
是否有意义?
回答:
1. LFENCE
在下面接受的答案中描述的情况下是必需的。
2. 方法(3)不应单独看待,而应结合前面的命令。例如方法(3):
MFENCE
MOV reg, [addr1] // LOAD-1
MOV [addr2], reg //STORE-1
MFENCE
MOV reg, [addr1] // LOAD-2
MOV [addr2], reg //STORE-2
我们可以将方法(3)的代码改写如下:
SFENCE
MOV reg, [addr1] // LOAD-1
MOV [addr2], reg //STORE-1
SFENCE
MOV reg, [addr1] // LOAD-2
MOV [addr2], reg //STORE-2
这里SFENCE
可以防止重新排序 STORE-1 和 LOAD-2。为此,在 STORE-1 命令 SFENCE
刷新 Store-Buffer 之后。
【问题讨论】:
有些指令带有“非临时提示”,不像通常的加载和存储那样有序;我想那些可能会从击剑中受益。 (编辑:这实际上是在您链接的页面上提到的。) 【参考方案1】:底线 (TL;DR):LFENCE
单独似乎对内存排序毫无用处,但它并不能使 SFENCE
替代 MFENCE
。问题中的“算术”逻辑不适用。
这里摘自Intel's Software Developers Manual, volume 3,第 8.2.2 节(2014 年 9 月的版本 325384-052US),与我在another answer 中使用的相同
读取不会与其他读取重新排序。 写入不会与较旧的读取一起重新排序。 对内存的写入不会与其他写入一起重新排序,但以下情况除外: 使用 CLFLUSH 指令执行写入; 使用非临时移动指令(MOVNTI、MOVNTQ、MOVNTDQ、MOVNTPS 和 MOVNTPD)执行的流式存储(写入);和 字符串操作(参见第 8.2.4.1 节)。 可以对不同位置的旧写入重新排序读取,但对同一位置的旧写入则不能。 无法使用 I/O 指令、锁定指令或序列化指令重新排序读取或写入。 读取无法通过早期的 LFENCE 和 MFENCE 指令。 写入无法通过早期的 LFENCE、SFENCE 和 MFENCE 指令。 LFENCE 指令无法通过较早的读取。 SFENCE 指令无法传递较早的写入。 MFENCE 指令无法传递较早的读取或写入。
从这里可以得出:
MFENCE
是一个完整的内存栅栏,适用于所有内存类型的所有操作,无论是否是非临时的。
SFENCE
仅防止重新排序写入(在其他术语中,它是 StoreStore 屏障),并且仅与非临时存储和列为例外的其他指令一起使用。
LFENCE
防止读取与后续读取和写入重新排序(即它结合了 LoadLoad 和 LoadStore 屏障)。但是,前两个项目符号表示 LoadLoad 和 LoadStore 屏障始终存在,没有例外。因此,单独的LFENCE
对内存排序毫无用处。
为了支持最后一个说法,我查看了所有 3 卷英特尔手册中提到 LFENCE
的所有地方,但没有发现任何地方会说 LFENCE
是内存一致性所必需的。甚至MOVNTDQA
——迄今为止唯一的非临时加载指令——提到了MFENCE
,但没有提到LFENCE
。
更新:请参阅Why is (or isn't?) SFENCE + LFENCE equivalent to MFENCE? 上的答案以获取以下猜测的正确答案
MFENCE
是否等于其他两个栅栏的“总和”是一个棘手的问题。乍一看,在三个栅栏指令中,只有 MFENCE
提供了 StoreLoad 屏障,即防止对较早写入的读取重新排序。但是正确的答案需要知道的不仅仅是上述规则;也就是说,重要的是所有围栏指令都相对于彼此进行排序。这使得SFENCE LFENCE
序列比单纯的单个效果的联合更强大:这个序列还可以防止 StoreLoad 重新排序(因为加载不能通过 LFENCE
,它不能通过 SFENCE
,它不能通过存储),因此构成了一个完整的内存栅栏(另请参阅下面的注释 (*))。但请注意,这里的顺序很重要,LFENCE SFENCE
序列没有相同的协同效应。
然而,虽然可以说MFENCE ~ SFENCE LFENCE
和LFENCE ~ NOP
,但这并不意味着MFENCE ~ SFENCE
。我故意使用等价 (~) 而不是等价 (=) 来强调算术规则不适用于此处。 SFENCE
后跟 LFENCE
的相互影响产生了差异;即使负载没有相互重新排序,也需要LFENCE
以防止使用SFENCE
重新排序负载。
(*) 说MFENCE
比其他两个栅栏的组合更强大仍然可能是正确的。特别是,英特尔手册第 2 卷中对 CLFLUSH
指令的注释说:“CLFLUSH
仅由 MFENCE
指令订购。不保证由任何其他围栏或序列化指令或其他指令订购CLFLUSH
指令。”
(更新,clflush
现在被定义为强排序(就像普通商店一样,所以如果你想稍后阻止加载,你只需要mfence
),但clflushopt
是弱的已订购,但可以用sfence
围起来。)
【讨论】:
谢谢,但我不同意“MFENCE
比LFENCE+SFENCE
更强。它是所有内存类型上所有操作的完整内存栅栏,无论是非时间性与否。”。我们总是可以写序列MOVNTQ
SFENCE
LFENCE
MOVNTQ
或MOV [addr], reg
SFENCE
LFENCE
MOV reg, [addr]
用于序列一致性。 IE。 LFENCE+SFENCE
- 类似地为所有内存类型的所有操作提供完整的内存栅栏,无论是否是非临时的。
感谢您的评论。它恰好更复杂;我用更多细节重写了答案。
感谢您的澄清,是的,我同意,即如果我们使用SFENCE
而不是MFENCE
来表示顺序一致性,那么会发生什么:MOV [addr1], reg
SFENCE
MOV reg, [addr2]
--> @ 987654375@MOV reg, [addr2]
SFENCE
-->MOV reg, [addr2]
MOV [addr1], reg
SFENCE
。 IE。这是合乎逻辑的,我们重新排序了 Store Load。但它真的会发生吗?如果是这样,那么一定有一个包含命令围栏的机制,这个机制是什么?到处都写着LFENCE
没有做任何事情,即没有这样的机制。有存储缓冲区,但没有加载缓冲区。
新的主要问题可能值得一个单独的 SO 问题。
谢谢!好的,我创建了新的单独问题:***.com/questions/27627969/…【参考方案2】:
考虑以下场景 - 这是推测性加载执行理论上会损害顺序一致性的关键情况
最初 [x]=[y]=0
CPU0: CPU1:
store [x]<--1 store [y]<--1
load r1<--[y] load r2<--[x]
由于 x86 允许将加载与较早的存储重新排序到不同的地址,因此两个加载都可能返回 0。在每个商店之后单独添加一个 lfence 不会阻止这种情况,因为它们只会阻止在同一上下文中重新排序,但是由于商店是在退休后调度的,因此您可以在执行和观察存储之前同时提交两个 lfence 和两个加载。
另一方面,mfence 会强制存储执行,然后才允许执行加载,因此您将在至少一个上下文中看到更新的数据。
至于 sfences - 正如评论中所指出的,理论上它不足以阻止负载在其上方重新排序,因此它可能仍会读取陈旧的数据。虽然就内存官方订购规则而言这是正确的,但我相信 x86 uarch 的当前实现使其稍微更强大(但我猜未来不会承诺这样做)。根据this description:
由于强大的 x86 排序模型,负载缓冲区被窥探 通过一致性流量。远程存储必须使所有其他副本无效 的缓存线。如果一个缓存行被一个负载读取,然后 被远程存储无效,加载必须被取消,因为它 可能读取无效数据。 x86 内存模型不需要 监听存储缓冲区。
因此,任何尚未提交到机器中的负载都应该可以被其他核心的 store 窥探,从而使 有效 观察到 commit 点的负载的时间,而不是 执行 点(这确实是无序的,可能已经执行得更早了)。提交是按顺序完成的,因此应该在之前的指令之后观察负载 - 就像我在上面在 cmets 中所说的那样,使 lfences 几乎没有用,因为没有它们可以以相同的方式保持一致性。 这主要是推测,试图解释 lfences 在 x86 中毫无意义的普遍概念——我不完全确定它的起源以及手头是否还有其他考虑因素——任何专家都会很高兴批准/挑战这个理论。
以上所有内容当然只适用于 WB mem 类型
【讨论】:
SFENCE 不能用于修复此示例,因为负载可以跨 SFENCE 向上迁移。该示例需要 MFENCE 来修复。 @ArchD.Robison,实际上,负载必须在之前的任何指令后退出,并且在此期间来自另一个线程的存储将能够窥探它 - 我认为这会导致重新-执行,但我不完全确定,所以我按照你的建议修复了答案 - 谢谢。 感谢您的修复。我建议根据架构保证进行编码,因为微架构师总是在寻找聪明而神秘的方法来加快速度。 @Leeor 你说的“但是因为商店是退休后派送的”是什么意思?您的意思是存储的结果先保存到存储缓冲区,然后再“分派”到 L1 缓存? @user997112,是的。存储的结果可以推测性地计算,但存储本身在提交之前不得在外部公开和可见(即 - 直到它的所有指令都已知在正确的路径上并且没有错误)。一些机器可以处理缓存中的此类存储,可能使用特殊位将它们标记为推测性的(以防出现窥探),但常见的解决方案是将存储保留在缓冲区中,直到它提交。另请注意,在 x86 等存储按顺序排列的架构中,从缓冲区分派也必须按顺序完成。以上是关于在 x86/x86_64 处理器上使用 LFENCE 指令是不是有意义?的主要内容,如果未能解决你的问题,请参考以下文章
armeabi-v7a arm64-v8a armeabi x86 x86_64区别
armeabi-v7a arm64-v8a armeabi x86 x86_64区别
armeabi-v7a arm64-v8a armeabi x86 x86_64区别
armeabi-v7a arm64-v8a armeabi x86 x86_64区别