英特尔内存模型是否使 SFENCE 和 LFENCE 变得多余?

Does the Intel Memory Model make SFENCE and LFENCE redundant?

英特尔内存模型保证:

http://bartoszmilewski.com/2008/11/05/who-ordered-memory-fences-on-an-x86/

我看到有人声称由于 Intel 内存模型,SFENCE 在 x86-64 上是多余的,但从未见过 LFENCE。上述内存模型规则是否使任一指令变得多余?

是的,LFENCE 和 SFENCE 在普通代码中没有用,因为 x86 对常规存储的获取/释放语义使它们变得多余,除非您使用其他特殊指令或内存类型。

对普通无锁代码来说唯一重要的栅栏是 locked 指令的完整栅栏(包括 StoreLoad),或慢速 MFENCE。对于顺序一致性存储,优先选择 xchg 而不是 mov+mfenceAre loads and stores the only instructions that gets reordered? 因为速度更快。

(是的,即使 with NT 指令,只要没有 WC 内存。)


Jeff Preshing 的 Memory Reordering Caught in the Act article is an easier-to-read description of the same case Bartosz's post talks about, where you need a StoreLoad barrier like MFENCE. Only MFENCE will do; you can't construct MFENCE out of SFENCE + LFENCE. (Why is (or isn't?) SFENCE + LFENCE equivalent to MFENCE?)

如果您在阅读 link 后有疑问,请阅读 Jeff Preshing 的其他博文。他们让我对这个主题有了很好的理解。 :) 虽然我认为我在 Doug Lea 的页面上发现了关于 SFENCE/LFENCE 通常是空操作的花絮。 Jeff 的帖子没有考虑 NT loads/stores.


相关:When should I use _mm_sfence _mm_lfence and _mm_mfence(我的回答和@BeeOnRope的回答都很好。我写这个答案的时间比那个答案早很多,所以这个答案的部分内容显示我几年前的经验不足。我的回答考虑了C++ 内部函数和 C++ 编译时内存顺序,这与 x86 asm 运行 时间内存顺序完全不同。但你仍然不想要 _mm_lfence()。)


SFENCE 仅在使用 movnt(非时间)流式存储 或使用类型设置为非正常写入的内存区域时才相关-后退。或者使用 clflushopt,这有点像弱序存储。 NT 存储绕过缓存以及弱排序。 x86's normal memory model is strongly ordered,NT 存储、WC(写组合)内存和 ERMSB 字符串操作(见下文)除外)。

LFENCE 只对弱排序负载的内存排序有用,非常 很少。 (或者对于在 before NT 商店进行常规加载的 LoadStore 排序可能吗?)

NT 加载(movntdqa) from WB memory are ,即使在不忽略 NT 提示的假设未来 CPU 中;在 x86 上进行弱排序加载的唯一方法是从弱读取时-有序内存 (WC),然后我认为只有 movntdqa。这在 "normal" 程序中不会偶然发生,所以如果你 mmap 视频 RAM 或其他东西,你只需要担心这个。

(lfence 的主要用例根本不是内存排序,它用于序列化指令执行,例如用于 Spectre 缓解,或使用 RDTSC。参见Is LFENCE serializing on AMD processors? 和该问题的 "linked questions" 边栏。)


C++ 中的内存排序,以及它如何映射到 x86 asm

几周前我对此感到好奇,并对最近的一个问题发表了相当详细的回答: 。我包含了很多 link 来介绍 C++ 的内存模型与硬件内存模型。

如果您使用 C++ 编写,使用 std::atomic<> 是告诉编译器您有什么排序要求的绝佳方式,因此它不会在编译时重新排序您的内存操作。您可以而且应该在适当的地方使用较弱的发布或获取语义,而不是默认的顺序一致性,这样编译器就不必在 x86 上发出任何屏障指令。它只需要按源顺序保持操作。


在 ARM 或 PPC 或带有 movnt 的 x86 等弱排序架构上,您需要在写入缓冲区和设置标志以指示数据已准备就绪之间使用 StoreStore 屏障指令。此外,reader 在检查标志和读取缓冲区之间需要一个 LoadLoad 屏障指令。

不计算 movnt,x86 在每个加载之间已经有 LoadLoad 障碍,在每个存储之间有 StoreStore 障碍。 (也保证了 LoadStore 排序)。 MFENCE 是所有 4 种障碍,包括 StoreLoad,这是 x86 默认情况下唯一不做的障碍。 MFENCE 确保加载不会使用其他线程看到您的商店之前的旧预取值,并且可能会使用他们自己的商店。 (同时也是 NT 商店订购和加载订购的障碍。)

有趣的事实:x86 lock-前缀指令也是完整的内存屏障。它们可以在旧的 32 位代码中用作 MFENCE 的替代品,这些代码可能 运行 on CPUs 不支持它。 lock add [esp], 0 在其他方面是空操作,并且在 L1 缓存中很可能很热并且已经处于 MESI 一致性协议的 M 状态的内存上执行 read/modify/write 循环。

SFENCE 是 StoreStore 屏障。在 NT 商店之后为后续商店创建发布语义很有用。

LFENCE 作为内存屏障几乎总是无关紧要的,因为唯一的弱顺序加载

一个 LoadLoad 和 also a LoadStore barrier。 (loadNT / LFENCE / storeNT 防止存储在加载之前变得全局可见。我认为如果加载地址是长依赖链的结果,或者是缓存中丢失的另一个加载的结果,这在实践中可能会发生。)


ERMSB 字符串操作

有趣的事实 #2(感谢 @EOF):来自 的存储是弱排序的(但不是缓存旁路)。 ERMSB 建立在常规 Fast-String Ops 的基础上(从 PPro 以来一直存在的 rep stos/movsb 的微编码实现的广泛存储)。

英特尔在其软件开发人员手册第 1 卷的第 7.3.9.3 节中记录了 ERMSB 存储 "may appear to execute out of order" 的事实。他们还说

"Order-dependent code should write to a discrete semaphore variable after any string operations to allow correctly ordered data to be seen by all processors"

他们没有提到 rep movsb 和存储到 data_ready 标志之间需要任何屏障指令。

我读它的方式,在 rep stosb / rep movsb 之后有一个隐式的 SFENCE(至少是字符串数据的栅栏,可能不是其他正在运行的弱排序 NT 存储)。无论如何,措辞意味着对标志/信号量的写入在所有字符串移动写入之后变得全局可见,因此在快速填充缓冲区的代码中不需要 SFENCE / LFENCE -string op 然后写入一个标志,或者在读取它的代码中。

(LoadLoad 排序总是发生,因此您总是按照其他 CPU 使其全局可见的顺序查看数据。即使用弱排序存储写入缓冲区不会改变加载的事实在其他线程中仍然是强烈排序的。)

总结:使用普通存储写入标志,指示缓冲区已准备就绪。 没有 reader 只需检查用 memset/memcpy.

写入的块的最后一个字节

我还认为 ERMSB 存储会阻止任何以后的存储传递它们,因此 如果您使用 movNT,您仍然只需要 SFENCE。即 rep stosb 作为一个整体具有发布语义。较早的说明。

有一个 MSR 位可以清除以禁用 ERMSB,以便新服务器需要 运行 旧二进制文件写入 "data ready" 标志作为 rep stosb 的一部分或 rep movsb 之类的。 (在那种情况下,我猜你会得到旧的快速字符串微码,它可能使用高效的缓存协议,但确实会让所有存储按顺序出现在其他核心上)。