Intel内存模型使SFENCE和LFENCE冗余吗?< / h1 >



英特尔内存模型保证:

  • 商店将不会与其他商店重新订购
  • 装载不会与其他装载重新排序

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

我曾见过由于Intel内存模型,SFENCE在x86-64上是冗余的说法,但从未见过LFENCE。上述内存模型规则是否使任一指令冗余?

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

对于普通的无锁代码来说,唯一重要的fence是来自locked指令的完整barrier(包括StoreLoad),或者是缓慢的MFENCE。顺序一致性存储首选xchg,而不是mov+mfence。加载和存储是唯一需要重新排序的指令吗?因为它更快。

假设没有非时序指令,' xchg '包含' mfence '吗?(是的,甚至NT指令,只要没有WC内存。)


Jeff Preshing的Memory Reordering Caught in the Act的文章是对Bartosz的文章中提到的相同情况的更容易阅读的描述,在这种情况下,你需要一个像MFENCE这样的StoreLoad屏障。只有MFENCE才行;你不能用SFENCE + LFENCE构造MFENCE。(为什么是(或isn't?)SFENCE + LFENCE等于MFENCE?)

如果你在阅读了你发布的链接后有疑问,请阅读Jeff Preshing的其他博客文章。他们使我对这门学科有了很好的了解。:)虽然我想我在Doug Lea的页面上发现了关于SFENCE/LFENCE的花边新闻。Jeff的帖子没有考虑NT加载/存储


相关:我什么时候应该使用_mm_sfence _mm_lfence和_mm_mfence(我的答案和@BeeOnRope的答案都很好。)我写这个答案的时间比那个答案早得多,所以这个答案的一部分显示了我多年前的经验不足。我的回答是考虑c++的内在特性和c++编译时内存顺序,这与x86 asm运行时内存顺序完全不同。但是你仍然不想要_mm_lfence()。)


SFENCE仅在使用movnt(非时态)流存储时相关。或者使用类型设置为普通Write-Back以外的其他类型的内存区域。或者是clflushopt,它有点像弱序存储。NT存储绕过缓存,并且是弱排序的。x86的正常内存模型是强排序的,除了NT存储、WC(写组合)内存和ERMSB字符串操作(见下文)。

LFENCE仅对弱顺序加载的内存排序有用,这种加载非常罕见. (或者可以在 NT stores之前使用常规加载的LoadStore排序?)

来自WB内存的NT加载(movntdqa)仍然是强有序的,即使在假设的未来CPU上不忽略NT提示;在x86上执行弱顺序加载的唯一方法是从弱顺序内存(WC)读取时,然后我认为只有movntdqa。这在"正常"程序中不会偶然发生,所以你只需要在映射视频RAM或其他东西时担心这个问题。

(lfence的主要用例根本不是内存排序,它是用于序列化指令执行,例如用于Spectre缓解,或与RDTSC. 参见LFENCE是否在AMD处理器上序列化?以及该问题的"链接问题"侧边栏


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

几个星期前,我对这个问题很好奇,并发布了一个相当详细的答案来回答最近的一个问题:原子操作,std::atomic<>写的顺序。我包含了很多关于c++内存模型和硬件内存模型的链接。

如果你是用c++写的,使用std::atomic<>是告诉编译器你有什么排序要求的好方法,所以它不会在编译时重新排序你的内存操作。您可以而且应该在适当的地方使用较弱的release或acquire语义,而不是默认的顺序一致性,这样编译器就不必在x86上发出任何屏障指令。它只需要保持操作的原始顺序。


在弱有序的体系结构上,如ARM或PPC,或带有movt的x86,在写入缓冲区和设置标志以指示数据准备就绪之间需要StoreStore屏障指令。此外,reader在检查标志和读取缓冲区之间需要LoadLoad barrier指令。

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

有趣的事实:x86lock前缀指令也是全内存屏障。它们可以在旧的32位代码中作为MFENCE的替代品,这些代码可能运行在不支持MFENCE的cpu上。lock add [esp], 0在其他方面是无操作的,它在内存上进行读/修改/写循环,这些内存很可能在L1缓存中很热,并且已经处于MESI一致性协议的M状态。

SFENCE是一个StoreStore屏障。在NT存储之后,为下面的存储创建发布语义是很有用的。

LFENCE作为内存屏障几乎总是不相关的,因为惟一的弱顺序加载

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


ERMSB字符串操作

有趣的事实#2(感谢@EOF):从ERMSB(增强rep movsb/rep stosb在IvyBridge和以后)的商店是弱排序的(但不是缓存绕过)。ERMSB基于常规的快速字符串操作(广泛存储自PPro以来一直存在的rep stos/movsb的微编码实现)。

英特尔在他们的软件开发人员手册第1卷的7.3.9.3节中记录了ERMSB存储"可能出现无序执行"的事实。他们还说

"顺序相关的代码应该写一个离散的信号量变量在任何字符串操作之后,允许看到正确排序的数据所有处理器">

他们没有提到在rep movsbdata_ready标志之间需要任何屏障指令。

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

(LoadLoad排序总是发生,所以你总是以其他cpu全局可见的顺序看到数据。也就是说,使用弱顺序存储写入缓冲区不会改变其他线程的加载仍然是强顺序的事实。

summary:使用一个普通的store写入一个标志,表示缓冲区已经就绪。不要让读程序只检查用memset/memcpy写的块的最后一个字节.

我也认为ERMSB存储阻止任何后续存储传递它们,所以如果你使用movNT,你仍然只需要SFENCE。. 也就是说,rep stosb作为一个整体具有发布语义wrt。早些时候指令。

有一个MSR位可以被清除以禁用ERMSB,以便新服务器需要运行旧的二进制文件,这些二进制文件将"数据准备好"标志作为rep stosbrep movsb或其他东西的一部分。(在这种情况下,我猜你会得到旧的快速字符串微码,它可能使用有效的缓存协议,但确实使所有的存储按顺序出现在其他核心上)。

最新更新