什么时候需要x86 LFENCE,SFENCE和MFENCE指令?

时间:2014-12-22 01:40:44

标签: multithreading assembly x86 cpu memory-fences

好的,我一直在阅读以下关于x86 CPU围栏的问题(LFENCESFENCEMFENCE):

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

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

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

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

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

NB阅读本主题的一个问题是,当我只对英特尔x86-64架构感兴趣时,“通常”为多CPU架构编写的文章数量很多。

1 个答案:

答案 0 :(得分:38)

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

  • 宽松
  • 消费
  • 获取
  • 推出
  • 获取释
  • 顺序

<强> C ++ 11:

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

<强> 86 / x86_64的:

<强> 1。获取 - 释放一致性然后,重要的是要理解在 x86 中访问常规RAM(默认标记为WB - 回写,与WT相同的效果(写入)通过使用asm MOV而没有任何其他命令自动为获取 - 释放一致性 - std::memory_order_acq_rel提供内存顺序。)或(UC(Uncacheable))。 即对于此内存而言,仅使用std::memory_order_seq_cst仅提供顺序一致性是有意义的。即,当您使用std::memory_order_relaxedstd::memory_order_acq_rel时,std::atomic::store()(或std::atomic::load())的已编译汇编代码将是相同的 - 仅MOV没有任何L/S/MFENCE {1}}。

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

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

<强> 2。顺序一致性但是要提供顺序一致性,您必须使用隐式(LOCK)或显式围栏(L / S / MFENCE),如下所述:Why GCC does not use LOAD(without fence) and STORE+SFENCE for Sequential Consistency?

  1. LOAD(没有围栏)和STORE + MFENCE
  2. LOAD(没有围栏)和LOCK XCHG
  3. MFENCE + LOADSTORE(没有围栏)
  4. LOCK XADD(0)和STORE(没有围栏)
  5. 例如,GCC使用1,但MSVC使用2. (但你必须知道,MSVS2012有一个错误:Does the semantics of `std::memory_order_acquire` requires processor instructions on x86/x86_64?

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

    规则例外:

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

    但也有一些例外:

    1. 如果我们将Page Table中的内存标记为Write Combined(POSIX中的ioremap_wc()),则automaticaly仅提供Acquire Consistency,我们必须按照以下段落进行操作。

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

    3.   
          
      • 写入内存不会与其他写入重新排序,以下异常:      
            
        • 使用CLFLUSH指令执行写入;
        •   
        • 使用非时间移动指令(MOVNTI,MOVNTQ,MOVNTDQ,MOVNTPS和MOVNTPD)执行的流存储(写入);和
        •   
        • 字符串操作(参见第8.2.4.1节)。
        •   
      •   

      在两种情况下1&amp; 2即使您希望获取 - 释放一致性,也必须在对同一地址的两次写入之间使用额外的SFENCE,因为此处自动只提供获取一致性,您必须自己执行发布(SFENCE)。

      回答你的两个问题:

        

      有时在进行存储时,CPU会写入其存储缓冲区   而不是L1缓存。但是,我不理解这些条款   哪个CPU会这样做?

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

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

      • 缓存L1 / 2/3 - 看起来像透明的关联数组(地址 - 值)。它速度快但不是最快,因为x86使用cache coherent协议MESIF / MOESI自动提供获取 - 发布一致性。它用于更简单的多线程编程,但会降低性能。 (实际上,我们可以使用Write Contentions Free算法和数据结构而不使用缓存相干,即没有MESIF / MOESI,例如PCI Express)。协议MESIF / MOESI在QPI上工作,它连接多处理器系统(ccNUMA)中不同CPU之间的CPU和内核中的内核。

        

      CPU2可能希望加载已写入CPU1的值   存储缓冲区。据我了解,问题是CPU2看不到了   CPU1&#c存储缓冲区中的新值。

      是。

        

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

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

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

      但是manualy在当前CPU-Core上刷新Store Buffer - 是的,你可以通过执行SFENCE命令来实现。您可以在两种情况下使用SFENCE

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

      注意:

      在x86 / x86_64的任何情况下我们都需要LFENCE吗? - 问题并不总是很清楚:Does it make any sense instruction LFENCE in processors x86/x86_64?

      其他平台:

      然后,您可以在理论上(对于真空中的球形处理器)使用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