什么时候需要 x86 LFence,SFENT 和 MFence 的指令?

好的,我已经阅读了以下关于 x86 CPU 篱笆(LFENCESFENCEMFENCE)的来自 SO 的 Qs:

以及:

老实说,我还是不太确定什么时候需要篱笆。我试图从移除完全开放的锁的角度来理解,并试图通过围栏使用更细粒度的锁,以尽量减少延迟。

首先,有两个具体的问题我不明白:

有时在进行存储时,CPU 将写入其存储缓冲区而不是 L1缓存。然而,我不明白的条款,一个 CPU 将这样做?

CPU2可能希望加载已写入 CPU1存储缓冲区的值。据我所知,问题在于 CPU2无法在 CPU1的存储缓冲区中看到新值。为什么 MESI 协议不能将刷新存储缓冲区作为其协议的一部分?

更一般地说,是否有人可以尝试描述整个场景,并帮助解释什么时候 LFENCE/MFENCESFENCE指令是必需的?

注意: 围绕这个主题阅读的一个问题是“通常”为多 CPU 体系结构编写的文章数量,而我只对 Intel x86-64体系结构特别感兴趣。

30386 次浏览

最简单的答案是: 必须使用3个篱笆(LFENCESFENCEMFENCE)中的一个来提供6个数据中的一个一致性:

  • 放松
  • 消耗
  • 获取
  • 放手
  • 获取-释放
  • 顺序

C + + 11:

最初,您应该从内存访问顺序的角度来考虑这个问题,这在 C + + 11中有很好的文档记录和标准化。你应该先读: http://en.cppreference.com/w/cpp/atomic/memory_order

X86/x86 _ 64:

1.获取-释放一致性: 然后,重要的是要理解,在 X86访问常规 RAM (默认标记为 WB-Write Back,与 WT (Write Thrthrough)或 UC (Uncheable)相同的效果)通过使用 asm MOV而不使用任何额外的命令 自动为获取-释放一致性提供内存顺序-std::memory_order_acq_rel。 也就是说,对于这个内存来说,仅仅使用 ABc0来提供循序一致性是有意义的。即当你使用: std::memory_order_relaxedstd::memory_order_acq_rel,然后为 std::atomic::store()(或 std::atomic::load())编译汇编代码将是相同的-只有 MOV没有任何 L/S/MFENCE

注意: 但是你必须知道,不仅 CPU,而且 C + +-编译器可以用内存重新排序操作,所有6个内存壁垒总是影响 C + +-编译器,而不管 CPU 架构如何。

那么,您必须知道,如何将它从 C + + 编译成 ASM (本机代码) ,或者如何在汇编程序上编写它。要提供任何一致性排除顺序你可以简单地写 MOV,例如 MOV reg, [addr]MOV [addr], reg等。

2.循序一致性: 但是为了提供循序一致性,你必须使用隐式(LOCK)或显式(L/S/MFENCE) ,正如这里所描述的: < a href = “ https://stackoverflow.com/questions/19047327/Why-GCC-does-not-use-loadwithout-fence-and-storesfence-for-stdmemory-order”> 为什么 gCC 不使用 LOAD (没有篱笆)和 STORE + SFENES 作为循序一致性?

  1. LOAD(无围栏)和 STORE + MFENCE
  2. (无围栏)和 LOCK XCHG
  3. MFENCE + LOADSTORE(无围栏)
  4. LOCK XADD(0)和 STORE(无围栏)

例如,GCC 使用1,而 MSVC 使用2.(但是你必须知道,MSVS2012有一个 bug: ‘ std: : memory _ order _ ’的语义是否需要 x86/x86 _ 64上的处理器指令?

然后,你可以阅读赫伯萨特,你的链接: https://onedrive.live.com/view.aspx?resid=4E86B0CF20EF15AD!24884&app=WordPdf&authkey=!AMtj_EflYn2507c

规则的例外:

对于使用 MOV访问默认标记为 WB-Write Back 的常规 RAM,这条规则是正确的。内存在 页表中标记,在每个 PTE (页表 Enrty)中标记每个页(4KB 连续内存)。

但也有一些例外:

  1. 如果我们将页表中的内存标记为“写入组合”(POSIX 中的 ioremap_wc()) ,那么自动只提供获得一致性,我们必须按照以下段落的方式行事。

  2. 请看我问题的答案: https://stackoverflow.com/a/27302931/1558037

  • 对内存的写操作不会与其他写操作一起重新排序,使用 以下例外情况:
    • 使用 CLFLUSH 指令执行写操作;
    • 用非时态移动指令(MOVNTI,MOVNTQ,MOVNTDQ,MOVNTPS 和 MOVNTPD)执行的流存储(写入) ; 以及
    • 字符串运算(见第8.2.4.1节)。

在这两种情况下1和2,你必须使用额外的 SFENCE之间的两个写到同一个地址,即使你想获得发布一致性,因为这里自动提供只获得一致性,你必须做发布(SFENCE)自己。

回答你的两个问题:

有时,在进行存储时,CPU 将写入其存储缓冲区 而不是 L1缓存。然而,我不明白的条款 哪个中央处理器会做这个?

从用户的角度来看,缓存 L1和存储缓冲区的作用是不同的。L1快,但存储缓冲更快。

  • Store-Buffer 是一个简单的队列,其中只存储写操作,不能重新排序-它是为了提高性能和隐藏访问缓存的延迟(L1-1ns,L2-3ns,L3-10ns)(CPU-Core 认为 Write 已经存储到缓存并执行下一个命令,但同时你的 Writes 只保存到 Store-Buffer,将保存到缓存 L1/2/3之后) ,即 CPU-Core 不需要等待,当 Writes 将被存储到缓存。

  • 缓存 L1/2/3-看起来像透明的关联数组(地址-值)。它很快,但不是最快的,因为 x86通过使用 缓存连贯性协议 MESIF/MOESI自动提供了获取-发布一致性。这样做是为了更简单的多线程编程,但降低了性能。(实际上,我们可以使用免费的算法和数据结构而不使用缓存相干,例如在 PCI Express上不使用 MESIF/MOESI)。协议 MESIF/MOESI 通过 QPI工作,QPI连接 CPU 中的核和多处理器系统中不同 CPU 之间的核(CcNUMA)。

CPU2可能希望加载已写入 CPU1的值 根据我的理解,问题是 CPU2无法看到 CPU1存储缓冲区中的新值。

是的。

为什么 MESI 协议就不能 包括刷新存储缓冲区作为其协议的一部分? ?

MESI 协议不能将刷新存储缓冲区作为其协议的一部分,因为:

  • MESI/MOESI/MESIF 协议与存储缓冲区无关,也不知道存储缓冲区的存在。
  • 在每次写入时自动刷新存储缓冲区会降低性能-并使其无用。
  • 在所有远程 CPU 上手动刷新存储缓冲区-核心(我们不知道在哪个核心存储缓冲区包含所需的写)通过使用一些命令-将降低性能(在8个 CPU x 15核心 = 120核心同时刷新存储缓冲区-这是可怕的)

但是在当前 CPU-Core 上手动刷新存储缓冲区-是的,你可以通过执行 SFENCE命令来完成。你可以在两种情况下使用 SFENCE:

  • 为 RAM 提供可缓存的写回循序一致性
  • 提供 规则的例外上的获取-释放一致性: 带写入组合缓存的 RAM,用 CLFLUSH 指令执行的写操作以及非时态 SSE/AVX 命令

注:

在 x86/x86 _ 64的任何情况下,我们需要 LFENCE吗?-问题并不总是很清楚: 它在 x86/x86 _ 64处理器中有任何意义吗?

其他平台:

然后,您可以从理论上(对于真空中的球形处理器)阅读 Store-Buffer 和 Invalid- Queue,它们是您的链接: http://www.puppetmastertrading.com/images/hwViewForSwHackers.pdf

以及如何在其他平台上提供循序一致性,不仅使用信用证和锁定,而且使用 LL/SC: http://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html