67

好的,我一直在阅读 SO 中关于 x86 CPU 围栏(和)的LFENCE以下SFENCE问题MFENCE

和:

老实说,我仍然不确定何时需要围栏。我试图从删除完全成熟的锁的角度来理解,并尝试通过栅栏使用更细粒度的锁定,以最大限度地减少延迟延迟。

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

有时,在进行存储时,CPU 会写入其存储缓冲区而不是 L1 缓存。但是,我不明白 CPU 会执行此操作的条款?

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

更一般地说,有人可以尝试描述整体情况并帮助解释何时/LFENCE需要说明吗?MFENCESFENCE

NB 围绕这个主题阅读的一个问题是,当我只对 Intel x86-64 架构特别感兴趣时,“一般”为多个 CPU 架构编写的文章数量。

4

1 回答 1

51

最简单的答案:您必须使用 3 个栅栏(LFENCESFENCEMFENCE)之一来提供 6 个数据一致性之一:

  • 轻松
  • 消耗
  • 获得
  • 发布
  • 获取-释放
  • 顺序的

C++11:

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

x86/x86_64:

1. Acquire-Release Consistency:那么,重要的是要了解在x86中访问常规RAM(默认标记为WB-Write Back,与WT(Write Throught)或UC(Uncacheable)效果相同)由MOV在没有任何附加命令的情况下使用 asm会自动为 Acquire-Release Consistency -提供内存顺序std::memory_order_acq_rel。即,此内存std::memory_order_seq_cst仅用于提供顺序一致性才有意义。即,当您使用时:std::memory_order_relaxed或者(or )std::memory_order_acq_rel的编译汇编代码将是相同的 - 只是没有任何.std::atomic::store()std::atomic::load()MOVL/S/MFENCE

注意:但您必须知道,不仅 CPU 和 C++ 编译器都可以使用内存重新排序操作,并且无论 CPU 架构如何,所有 6 个内存屏障都会影响 C++ 编译器。

那么,你必须知道,如何将它从 C++ 编译为 ASM(本机机器码),或者如何在汇编器上编写它。要提供任何 Consistency exclude Sequential 您可以简单地编写MOV,例如MOV reg, [addr]MOV [addr], reg

2. 顺序一致性:但是要提供顺序一致性,您必须使用隐LOCKMFENCE

  1. LOAD(不带围栏)和STORE+MFENCE
  2. LOAD(没有围栏)和LOCK XCHG
  3. MFENCE+LOADSTORE(没有围栏)
  4. LOCK XADD( 0 ) 和STORE(没有围栏)

例如,GCC 使用 1,而 MSVC 使用 2。(但你必须知道,MSVS2012 有一个 bug:`std::memory_order_acquire` 的语义是否需要 x86/x86_64 上的处理器指令?

然后,您可以阅读 Herb Sutter,您的链接:https ://onedrive.live.com/view.aspx?resid=4E86B0CF20EF15AD!24884&app=WordPdf&authkey=!AMtj_EflYn2507c

规则的例外:

此规则适用于使用MOV默认标记为 WB - 回写的常规 RAM 进行访问。内存在Page Table中标记,在每个 PTE(Page Table Enrty)中为每个 Page(4 KB 连续内存)。

但也有一些例外:

  1. 如果我们将页表中的内存标记为写入组合(ioremap_wc()在 POSIX 中),则自动仅提供 Acquire Consistency,我们必须按照以下段落操作。

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

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

在第 1 和第 2 种情况下,SFENCE即使您想要获取-发布一致性,您也必须在两次写入同一个地址之间使用额外的内容,因为这里自动仅提供获取一致性,您必须SFENCE自己执行发布 ()。

回答你的两个问题:

有时,在进行存储时,CPU 会写入其存储缓冲区而不是 L1 缓存。但是,我不明白 CPU 会执行此操作的条款?

从用户的角度来看,缓存 L1 和 Store Buffer 的作用不同。L1 快,但 Store-Buffer 更快。

  • 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自动提供 Acquire-Release Consistency 。这样做是为了更简单的多线程编程,但会降低性能。(确实,我们可以使用无写入争用算法和数据结构,而无需使用缓存一致性,即无需 MESIF/MOESI,例如通过PCI Express)。协议 MESIF/MOESI 通过QPI工作,它连接 CPU 中的核心和多处理器系统 ( ccNUMA )中不同 CPU 之间的核心。

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

是的。

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

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

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

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

  • 通过可写回缓存在 RAM 上提供顺序一致性
  • 在规则例外情况下提供获取-释放一致性:具有写入组合缓存的 RAM,用于使用 CLFLUSH 指令执行的写入以及用于非临时 SSE/AVX 命令

笔记:

我们LFENCE在任何情况下都需要 x86/x86_64 吗?- 问题并不总是很清楚:处理器 x86/x86_64 中的指令 LFENCE 是否有意义?

其他平台:

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

以及如何在其他平台上提供顺序一致性,不仅使用 L/S/MFENCE 和 LOCK,而且使用LL/SChttp ://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html

于 2014-12-24T10:37:10.020 回答