在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中:
lfence
:( CoreX-L1) - > (CoreX-L2) - > L3-> (Core1-L2) - > (核心-1-L1)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
吗?
答案 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)已经(或至少有)与mfence
和sfence
共享的单一实现(即刷新存储缓冲区),因此使用较弱的产品没有性能优势sfence
。
BTW,请注意,每次写入共享变量后都不必刷新;您只需要在写入和后续读取其他共享变量之间进行刷新。