如果我不使用栅栏,一个核心需要多长时间才能看到另一个核心的写入?

If I don't use fences, how long could it take a core to see another core's writes?

我一直在尝试 Google 我的问题,但老实说我不知道​​如何简洁地陈述问题。

假设我在多核英特尔系统中有两个线程。这些线程 运行 在同一个 NUMA 节点上。假设线程 1 写入 X 一次,然后只是偶尔向前读取它。进一步假设,除其他事项外,线程 2 连续读取 X。如果我不使用内存栅栏,那么线程 1 写入 X 和线程 2 看到更新值之间需要多长时间?

我知道 X 的写入将进入存储缓冲区,然后从那里进入缓存,此时 MESIF 将启动,线程 2 将通过 QPI 看到更新后的值。 (或者至少这是我收集到的)。我假设存储缓冲区会在存储栅栏上写入缓存,或者如果该存储缓冲区条目需要重用,但我不知道存储缓冲区是否分配给写入。

最终我要自己回答的问题是,线程 2 是否有可能在执行其他工作的相当复杂的应用程序中几秒钟内看不到线程 1 的写入。

内存屏障不会让其他线程更快地看到您的存储 any(除了阻止稍后加载可以稍微减少对提交缓冲存储。)

存储缓冲区总是尝试尽快将退休的(已知的非推测性)存储提交到 L1d 缓存。缓存是连贯的 1,因此由于 MESI/MESIF/MOESI 而使它们全局可见。 并未设计为适当的缓存或写入组合缓冲区(尽管它可以将背靠背存储组合到同一缓存行),因此它需要清空自身以为新存储腾出空间。与缓存不同,它希望自己保持空而不是满。

注 1:不只是 x86;任何 ISA 的所有多核系统,我们可以在其内核中 运行 Linux 的单个实例必然是缓存一致的; Linux 依靠 volatile 的手滚原子使数据可见。类似地,C++ std::atomic load/store 与 mo_relaxed 的操作只是普通的 asm 加载和存储在所有正常的 CPU 上,依赖硬件来实现核心之间的可见性,而不是手动刷新。 When to use volatile with multi threading? 解释一下。有一些集群或具有非一致性共享内存的混合微控制器+DSP ARM 板,但我们不会 运行 跨不同一致性域的同一进程的线程。相反,您 运行 每个集群节点上的单独 OS 实例。我不知道 atomic<T> loads/stores 包含手动刷新指令的任何 C++ 实现。 (有的话请告诉我。)


Fences/barriers 通过让当前线程等待来工作

...直到通过正常机制发生所需的任何可见性。

完整屏障(mfencelocked 操作)的一个简单实现是停止管道直到存储缓冲区耗尽,但高性能实现可以做得更好并允许-顺序执行与内存顺序限制分开。

(不幸的是 Skylake's mfence does fully block out-of-order execution,以修复模糊的 SKL079 错误,涉及从 WC 内存加载 NT。但是 lock addxchg 或任何只会阻止以后加载读取 L1d 或存储缓冲区,直到屏障到达存储缓冲区的末尾。并且 mfence 在较早的 CPU 上可能也没有这个问题。)


一般来说,在非 x86 架构上(对于较弱的内存屏障有明确的 asm 指令,比如 only StoreStore fences 而不关心负载),原则是相同的:阻塞任何它需要阻塞的操作,直到这个核心已完成任何类型的早期操作。

相关:

  • 讨论负载变得全局可见/负载数据来自何处的含义。

  • Does a memory barrier acts both as a marker and as an instruction?

  • When to use volatile with multi threading? - 基本上从不,这只是一种使用 std::memory_order_relaxed 滚动自己的 std::atomic<T> 的方法,因为缓存一致性。

  • - 什么是存储缓冲区,以及它们存在的原因。


Ultimately the question I'm trying to answer for myself is if it is possible for thread 2 to not see thread 1's write for several seconds

不,最坏情况下的延迟可能是存储缓冲区长度 (56 entries on Skylake, up from 42 in BDW) 乘以缓存未命中延迟,因为 x86 的强大内存模型(没有 StoreStore 重新排序)需要存储按顺序提交.但是多个缓存行的 RFO 可以同时运行,所以最大延迟可能是它的 1/5(保守估计:有 10 个 Line Fill Buffers)。也可能存在来自正在运行的负载(或来自其他核心)的争用,但我们只想要一个数量级的封底数字。

假设 RFO 延迟(DRAM 或来自另一个内核)是 3GHz CPU 上的 300 个时钟周期(基本上是弥补的)。因此,最坏情况 商店变得全局可见的延迟可能类似于 300 * 56 / 5 = 3360 个核心时钟周期。所以 在一个数量级内,我们假设的 3GHz CPU 上的最坏情况约为 ~1 微秒。 (CPU 频率抵消,因此以纳秒为单位的 RFO 延迟估计会更有用)。

那时 所有 您的商店需要等待很长时间才能收到 RFO,因为它们 所有 到未缓存的位置或由其他核心拥有。其中 none 背靠背到同一个缓存行,因此 none 可以合并到存储缓冲区中。所以通常你会期望它明显更快。

我认为没有任何合理的机制可以让它花费一百微秒,更不用说一整秒了。

如果您的所有存储都缓存行,而其他内核都在争用同一行的访问权限,那么您的 RFO 可能需要比正常情况下更长的时间,因此可能需要数十微秒,甚至可能是一百微秒。但那种绝对最坏的情况不会偶然发生。