Linux内核使用lock; addl $0,0(%%esp)
作为写屏障,而RE2库使用xchgl (%0),%0
作为写屏障。有什么区别,哪个更好?
x86是否也需要读屏障指令?RE2将其读屏障函数定义为x86上的无操作,而Linux根据SSE2是否可用将其定义为lfence
或无操作。什么时候需要lfence
?
发布于 2010-11-21 05:58:15
如果我们在(%%esp)地址测试变量的0状态,“lock lock;addl $0,0(%%esp)”会更快。因为我们将0值添加到锁定变量,并且如果地址(%%esp)处的变量的锁定值为0,则零标志被设置为1。
英特尔数据表中的lfence:
对在LFENCE指令之前发出的所有从内存加载指令执行序列化操作。此序列化操作可确保在LFENCE指令之后的任何加载指令全局可见之前,程序顺序中位于LFENCE指令之前的每个加载指令都是全局可见的。
(编者按: 或 lock
**ed操作是保证顺序一致性的唯一有用的屏障(在存储之后)**。lfence
不会阻止存储缓冲区对StoreLoad进行重新排序。)
例如:像'mov‘这样的内存写指令是原子的(它们不需要锁前缀),如果它们正确对齐的话。但此指令通常在CPU缓存中执行,并且此时对所有其他线程都不是全局可见的,因为必须首先执行内存围栏,以使此线程等待,直到先前的存储对其他线程可见。
因此,这两条指令之间的主要区别在于,xchgl指令不会对条件标志产生任何影响。当然,我们可以使用lock cmpxchg指令测试锁定变量的状态,但这仍然比使用lock add $0指令复杂。
发布于 2010-11-21 06:00:23
引用IA32手册(第3A卷,第8.2章:内存排序):
在单处理器系统中,对于被定义为可回写高速缓存的存储器区域,存储器排序模型遵循以下原则。
一节
对不同位置的较旧写入可能会对
LFENCE
且MFENCE
SFENCE
无法通过MFENCE
and注意:上面的“在单处理器系统中”有点误导。相同的规则分别适用于每个(逻辑)处理器;然后手册继续描述多个处理器之间的其他排序规则。与这个问题有关的唯一一点是
简而言之,只要您正在写回写内存(只要您不是驱动程序或图形程序员,您就会看到所有的内存),大多数x86指令几乎是顺序一致的- x86 CPU可以执行的唯一重新排序是在写入之前对稍后(独立)的读取进行重新排序。关于写屏障的主要问题是,它们有一个lock
前缀(隐式或显式),它禁止所有重新排序,并确保多处理器系统中的所有处理器以相同的顺序看到操作。
此外,在回写式内存中,读操作永远不会重新排序,因此不需要设置读障碍。最近的x86处理器对于流存储和写组合存储器(通常用于映射图形存储器)具有较弱的存储器一致性模型。这就是各种fence
指令发挥作用的地方;它们对于任何其他内存类型都不是必需的,但是Linux内核中的一些驱动程序确实处理写组合内存,所以它们只是以这种方式定义了它们的读屏障。IA-32手册的第3A卷11.3.1节中列出了每种内存类型的订购型号。简而言之:直写、回写和写保护允许推测性读取(遵循上面详细介绍的规则),不可缓存和强不可缓存内存具有强大的排序保证(没有处理器重新排序,读/写立即执行,用于MMIO)和写组合内存具有弱排序(即需要栅栏的宽松排序规则)。
发布于 2018-10-21 06:32:21
lock addl $0, (%esp)
是mfence
的替代品,而不是lfence
。
其用例是需要阻塞StoreLoad重新排序(x86的强大内存模型允许的唯一一种),但不需要对共享变量执行原子RMW操作。https://preshing.com/20120515/memory-reordering-caught-in-the-act/
例如,假设std::atomic<int> a,b
对齐
movl $1, a a = 1; Atomic for aligned a
# barrier needed here
movl b, %eax tmp = b; Atomic for aligned b
您可以选择:
使用
xchg
,进行顺序一致性存储,例如mov $1, %eax
/ xchg %eax, a
,因此您不需要单独的屏障;它是存储的一部分。我认为在大多数现代硬件上,这是最有效的选择;除了gcc之外,C++11编译器将xchg
用于seq_cst存储,而mfence
作为屏障。(gcc将mov
+ mfence
用于seq_cst商店),lock addl $0, (%esp)
作为屏障。任何lock
ed指令都是一个完全的障碍。Does lock xchg have the same behavior as mfence?(或者移到其他位置,但是在L1d中堆栈几乎总是私有的和热门的,所以它是一个比较好的候选者。但是,这可能会使用堆栈底部的数据为某些内容创建一个依赖链。)
您只能通过将xchg
折叠到存储中来将其用作屏障,因为它使用不依赖于旧值的值无条件地写入内存位置。
在可能的情况下,将xchg
用于seq-cst存储可能是最好的,即使它也从共享位置读取。在最近的英特尔CPU (Are loads and stores the only instructions that gets reordered?)上,mfence
的速度比预期的要慢,也会像lfence
一样无序地阻止独立的非内存指令的执行。
即使在mfence
可用的情况下,使用lock addl $0, (%esp)/(%rsp)
而不是mfence
也是值得的,但我还没有尝试过它的缺点。使用-64(%rsp)
或其他东西可能不太可能延长数据对热门内容(本地或返回地址)的依赖,但这可能会使valgrind等工具感到不快。
lfence
永远不会对内存排序有用,除非你正在使用MOVNTDQA加载从视频内存(或其他WC弱排序区域)读取数据。
串行化乱序执行(而不是存储缓冲区)对于停止StoreLoad重新排序(这是x86的强大内存模型允许正常的WB (回写)内存区域的唯一一种)没有用。
lfence
的真实用例是用于阻止rdtsc
的无序执行,以便对非常短的代码块进行计时,或者通过条件分支或间接分支阻止推测来缓解频谱。
另请参阅When should I use _mm_sfence _mm_lfence and _mm_mfence (我的答案和@BeeOnRope的答案),了解更多关于为什么lfence
没有用处,以及何时使用每个屏障指令的更多信息。(或者在我的例子中,当使用C++而不是asm进行编程时,使用C++内部函数)。
https://stackoverflow.com/questions/4232660
复制相似问题