为什么x86 / x86_64上的顺序语义通过MOV [addr],reg + MFENCE而不是+ SFENCE使用?

时间:2013-09-25 10:00:20

标签: c++ multithreading assembly concurrency x86

在Intel x86 / x86_64系统中,有3种类型的内存屏障:lfence,sfence和mfence。关于它们的使用问题。 对于顺序语义(SC),足以将MOV [addr], reg + MFENCE用于需要SC语义的所有存储器单元。但是,您可以编写整体代码,反之亦然:MFENCE + MOV reg, [addr]。显然,如果内存的存储数量通常小于它的负载,那么使用写入屏障的总成本就会降低。在此基础上,我们必须使用顺序存储到内存,进行另一个优化 - [LOCK] XCHG,由于“XCHG内部的MFENCE”仅适用于内存中使用的内存缓存行,因此可能更便宜XCHG(video where on 0:28:20 said that MFENCE more expensive that XCHG)。

http://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html

  

C / C ++ 11操作x86实现

     
      
  • 加载Seq_Cst:MOV(来自内存)
  •   
  • Store Seq Cst:(LOCK)XCHG //   替代方案:MOV(进入记忆),MFENCE
  •   

注意:有一个C / C ++ 11到x86的替代映射,而不是锁定(或隔离)Seq Cst存储锁/隔离Seq Cst加载:

  
      
  • 加载Seq_Cst:LOCK XADD(0)//替代:MFENCE,MOV(来自内存)
  •   
  • 存储Seq Cst:MOV(进入内存)
  •   

不同之处在于ARM和Power内存屏障仅与LLC(最后一级缓存)进行交互,x86与低级缓存L1 / L2进行交互。 在x86 / x86_64中:

    Core1上的
  • lfence :( CoreX-L1) - > (CoreX-L2) - > L3-> (Core1-L2) - > (核心-1-L1)
  • Core1上的
  • sfence :( Core1-L1) - > (Core1-L2) - > L3-> (CoreX-L2) - > (COREX-L1)

在ARM中:

  • ldr; dmb;:L3-> (Core1-L2) - > (核心-1-L1)
  • dmb; str; dmb; :( Core1-L1) - > (Core1-L2) - > L3

GCC 4.8.2编译的C ++ 11代码 - x86_64中的GDB:

std::atomic<int> a;
int temp = 0;
a.store(temp, std::memory_order_seq_cst);
0x4613e8  <+0x0058>         mov    0x38(%rsp),%eax
0x4613ec  <+0x005c>         mov    %eax,0x20(%rsp)
0x4613f0  <+0x0060>         mfence

但为什么 x86 / x86_64 顺序语义(SC)使用MOV [addr], reg + MFENCE而不是MOV [addr], reg + SFENCE,为什么我们需要全屏MFENCE那里有SFENCE吗?

2 个答案:

答案 0 :(得分:2)

sfence不会阻止StoreLoad重新排序。除非有任何新台币商店在飞行,否则从结构上来讲这是无人操作的。由于x86不允许StoreStore重新排序,因此商店已经在等待较早的商店提交它们自己的L1d并成为全局可见的对象之前提交了。 (NT存储区/存储到WC存储器中除外)

对于seq_cst,您需要一个完整的屏障来刷新存储缓冲区/确保所有旧存储在所有 之前都是全局可见的。请参见https://preshing.com/20120515/memory-reordering-caught-in-the-act/一个示例,其中在实践中未使用mfence会导致顺序不一致的行为,即内存重新排序。


如您所见,可以在每个seq_cst负载上而不是每个seq_cst存储/ RMW上将seq_cst映射到具有完全屏障的x86 asm。在这种情况下,您不需要在商店上使用任何屏障说明(因此它们将具有发布语义),但是您需要在每个mfence之前添加atomic::load(seq_cst)

答案 1 :(得分:-1)

您不需要mfence; sfence确实足够了。实际上,除非您正在处理设备,否则在x86中永远不需要lfence。但是英特尔(我认为AMD)已经(或至少有)与mfencesfence共享的单一实现(即刷新存储缓冲区),因此使用较弱的产品没有性能优势sfence

BTW,请注意,每次写入共享变量后都不必刷新;您只需要在写入和后续读取其他共享变量之间进行刷新。