英特尔内存模型保证:
- 存储操作不会与其他存储操作重排序
- 加载操作不会与其他加载操作重排序
http://bartoszmilewski.com/2008/11/05/who-ordered-memory-fences-on-an-x86/
我曾经看到过有人声称在x86-64上SFENCE指令是多余的,但从未听说过LFENCE是否也是多余的。上述内存模型规则是否使这两条指令都变成了多余的?
英特尔内存模型保证:
http://bartoszmilewski.com/2008/11/05/who-ordered-memory-fences-on-an-x86/
我曾经看到过有人声称在x86-64上SFENCE指令是多余的,但从未听说过LFENCE是否也是多余的。上述内存模型规则是否使这两条指令都变成了多余的?
Jeff Preshing的Memory Reordering Caught in the Act文章是Bartosz帖子讨论的同一案例的易读版本,在这种情况下,您需要像MFENCE这样的StoreLoad屏障。只有MFENCE才行;您不能使用SFENCE + LFENCE构建MFENCE。(为什么SFENCE + LFENCE等同于MFENCE还是不等同于MFENCE?)
如果您在阅读您发布的链接后有疑问,请阅读Jeff Preshing的其他博客文章。它们使我对这个主题有了很好的理解。:) 尽管我认为我在Doug Lea的页面中找到了关于SFENCE/LFENCE通常是无操作的提示。Jeff的帖子没有考虑NT负载/存储。
相关: 何时使用_mm_sfence、_mm_lfence和_mm_mfence (我和@BeeOnRope的回答都很好。我写这个答案比那个答案早得多,所以这个答案的部分显示了我多年前的经验不足。我在那里的回答考虑了C++内置函数和C++编译时内存顺序,这与x86汇编运行时内存顺序完全不同。但你仍然不想要_mm_lfence()
。)
SFENCE 只在使用 movnt
(非临时)流式存储或者处理类型设置为非正常写回的内存区域时才相关。或者使用类似于弱序存储的 clflushopt
。NT 存储既绕过了缓存又是弱序的。x86 的正常内存模型是强序的,除了 NT 存储、WC(写合并)内存和 ERMSB 字符串操作(见下文)。
LFENCE 只对弱序加载的内存排序有用,这种情况非常罕见。(或者可能适用于普通加载 之前 的 LoadStore 排序与 NT 存储?)
NT从WB内存中加载(movntdqa
)仍然是强顺序的,即使在一个假设的未来CPU上,该CPU不会忽略NT提示;在x86上进行弱有序加载的唯一方法是从弱有序内存(WC)读取,然后我认为只能使用movntdqa
。这在"正常"程序中不会意外发生,因此只有在映射视频RAM或类似情况下才需要担心。lfence
的主要用途根本不是内存排序,而是用于序列化指令执行,例如用于Spectre缓解或与RDTSC配合使用。请参见{{link3:AMD处理器上的LFENCE序列化吗?}}和该问题的“相关问题”侧边栏。)
我几周前对此产生了兴趣,并在最近的一个问题中发布了一个相当详细的答案: 原子操作,std::atomic<>和写入顺序。 我包括了很多关于C++内存模型与硬件内存模型的内容链接。
如果您正在使用C++编写代码,使用std::atomic<>
是告诉编译器您具有哪些排序要求的绝佳方式,因此它不会在编译时重新排列您的内存操作。 在适当的情况下,您可以使用较弱的释放或获取语义,而不是默认的顺序一致性,这样编译器就不必在x86上发出任何屏障指令。 它只需按源顺序保留操作即可。
SFENCE是一个StoreStore障碍。在NT存储后使用它可以为后续的存储创建发布语义。
LFENCE几乎总是与内存障碍无关,因为唯一的弱序加载
一个LoadLoad和还有一个LoadStore障碍。(loadNT / LFENCE / storeNT
防止存储在加载之前变为全局可见。我认为如果加载地址是长依赖链的结果,或者是另一个未命中缓存的加载的结果,在实践中可能会发生这种情况。)
有趣的事实#2(感谢@EOF
):ERMSB(增强的rep movsb
/rep stosb
在IvyBridge及更高版本上)的存储是弱序的(但不是缓存旁路)。 ERMSB基于常规快速字符串操作(从已存在自PPro以来的rep stos/movsb
的微代码实现中广泛存储)。
英特尔在其软件开发人员手册第1卷的第7.3.9.3节中记录了ERMSB存储“可能看起来是无序的”。 他们还说
“依赖顺序的代码应在任何字符串操作之后写入独立信号量变量,以允许所有处理器正确有序地查看数据”
他们没有提到在rep movsb
和将数据存储到data_ready
标志之间需要任何屏障指令。
rep stosb / rep movsb
之后,隐含着一个SFENCE(至少是字符串数据的栅栏,可能不包括其他正在进行中的弱序NT存储)。总之,措辞意味着在所有字符串移动写入之后,对标志/信号量的写入变得全局可见,因此在使用快速字符串操作填充缓冲区然后写入标志的代码中,不需要SFENCE/LFENCE,或者在读取它的代码中也不需要。movNT
,则仍然只需要SFENCE。即rep stosb
作为整体具有与早期指令相关的发布语义。
有一个MSR位可以被清除,以禁用ERMSB,以使新服务器能够运行写入“数据准备就绪”标志的旧二进制文件,例如rep stosb
或rep movsb
等。在这种情况下,我想您会获得旧的快速字符串微码,它可能使用高效的缓存协议,但确实使所有存储看起来对其他内核按顺序。
movnt
指令具有较弱的内存序,memcpy/strcpy
指令 (rep[ne] movs[b/w/d/q]
) 也是如此。 - EOFrep movsb
之后,有一个隐式的StoreStore屏障(用于字符串数据),因此您只需要单独编写数据准备标志(而不是作为字符串操作的最后几个字节)。 - Peter Cordesmovnt
和rep[ne] movs[b/w/d/q]
; 而是(潜在地)每一个访问内存的指令; 因为可以通过将PAT/页面表或MTRRs配置为正在访问的内存的“写组合”(而不是“写回”),从而削弱内存排序模型。 - Brendan
mfence/sfence/lfence
。 - Iwillnotexist Idonotexistmovnt
加载/存储以那种方式重新排序,最可能是如果加载地址直到存储之后才可用。我没有仔细研究过LFENCE,并且认为它只是LoadLoad屏障。 - Peter Cordes