Linux内核使用lock; addl $0,0(%%esp)
作为写屏障,而RE2库使用xchgl (%0),%0
作为写屏障。有什么区别,哪个更好?
x86是否还需要读屏障指令? RE2将其读取屏障功能定义为x86上的无操作,而Linux将其定义为lfence
或无操作,具体取决于SSE2是否可用。何时需要lfence
?
答案 0 :(得分:10)
引自IA32手册(第3A卷,第8.2章:记忆订购):
在用于内存区域的单处理器系统中,内存区域定义为可回写可缓存,内存排序模型遵循以下原则[..]
- 读取不会与其他读取重新排序
- 写入不会与较旧的读取重新排序
- 写入内存不会与其他写入重新排序,但
- 使用
CLFLUSH
指令执行的写入- 使用非时间移动指令([此处的指令列表])执行的流式存储(写入)
- 字符串操作(参见第8.2.4.1节)
- 可以使用较旧的写入对不同位置进行重新排序,但不能将较旧的写入重新排序到同一位置。
- 读取或写入不能使用I / O指令,锁定指令或序列化指令重新排序
- 读取无法通过
LFENCE
和MFENCE
说明- 写入无法通过
SFENCE
和MFENCE
说明
注意:上面的“在单处理器系统中”有点误导。每个(逻辑)处理器都有相同的规则;然后,手册继续描述多个处理器之间的附加排序规则。与此问题有关的唯一一点是
- 锁定的说明总订单。
简而言之,只要您正在写回写内存(只要您不是驱动程序或图形程序员,就会看到所有内存),大多数x86指令几乎都是顺序一致的 - x86 CPU可以执行的唯一重新排序是在写入之后重新排序(独立)读取以执行。关于写入障碍的主要问题是它们具有lock
前缀(隐式或显式),禁止所有重新排序,并确保多处理器系统中的所有处理器以相同的顺序查看操作。 / p>
此外,在回写存储器中,读取永远不会重新排序,因此不需要读取障碍。最近的x86处理器具有较弱的内存一致性模型,适用于流存储和写入组合内存(通常用于映射图形内存)。这就是各种fence
指令发挥作用的地方;它们对于任何其他内存类型都不是必需的,但Linux内核中的某些驱动程序确实处理了写入组合内存,因此它们只是以这种方式定义了它们的读取障碍。每种存储器类型的排序模型列表在第11.3.1节中。 IA-32手册的3A。短版本:Write-Through,Write-Back和Write-Protected允许推测性读取(遵循上面详述的规则),Uncachable和Strong Uncacheable内存具有强大的排序保证(没有处理器重新排序,读取/写入立即执行,用于MMIO并且写入组合内存具有弱排序(即需要围栏的宽松排序规则)。
答案 1 :(得分:8)
如果我们在(%% esp)地址测试锁定变量的0状态,“锁定; addl $ 0,0(%% esp)”会更快。因为我们向lock变量添加0值,如果地址(%% esp)的变量的锁定值为0,则零标志设置为1。
lfence :
执行序列化操作 所有从内存加载的指令 在LFENCE之前发布 指令。这序列化 操作保证每次加载 程序之前的指令 命令LFENCE指令是 在任何负载之前全局可见 遵循LFENCE的指令 指令在全球范围内可见。
(编者注:mfence
或lock
ed操作是连续一致性唯一有用的围栏(在商店之后)。lfence
执行不阻止StoreLoad由商店缓冲区重新排序。)
例如:如果正确对齐,像'mov'这样的内存写指令是原子的(它们不需要锁前缀)。但是这条指令通常在CPU缓存中执行,此时所有其他线程都不会全局可见,因为必须首先执行内存栅栏才能使此线程等待,直到其他线程可以看到以前的存储。
因此,这两条指令的主要区别在于 xchgl 指令对条件标志没有任何影响。当然我们可以使用 lock cmpxchg 指令测试锁定变量状态,但这仍然比使用 lock add $ 0 指令更复杂。
答案 2 :(得分:7)
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编译器对seq_cst存储区使用xchg
。mfence
作为障碍。 (gcc将mov
+ mfence
用于seq_cst存储)。使用lock addl $0, (%esp)
作为障碍。任何lock
版本的指令都是完全障碍。 Does lock xchg have the same behavior as mfence?
(或到其他位置,但是堆栈在L1d中几乎总是私有且很热,因此它是一个不错的选择。但是,这可能会使用堆栈底部的数据为某些对象创建依赖链。)< / p>
您只能将xchg
用作屏障,方法是将其折叠到存储区中,因为它无条件地使用不依赖于旧值的值来写入内存位置。
在可能的情况下,最好将xchg
用于seq-cst存储,即使它也从共享位置读取也是如此。 mfence
的速度比最近的Intel CPU(Are loads and stores the only instructions that gets reordered?)的速度慢,并且也与lfence
一样,阻止了对独立非内存指令的无序执行。
即使lock addl $0, (%esp)/(%rsp)
可用,也可能值得使用mfence
而不是mfence
,但是我还没有尝试过不利之处。使用-64(%rsp)
或其他方法可能不太可能延长对某些热点(本地地址或返回地址)的数据依赖,但这会使valgrind之类的工具不满意。
lfence
永远不会对内存排序有用,除非您从加载MOVNTDQA的视频RAM(或其他WC弱排序区域)中读取数据。
序列化无序执行(但不是存储缓冲区)对于停止StoreLoad重新排序(x86的强大内存模型允许普通WB(写回)内存区域的唯一类型)没有用。
lfence
的实际用例是阻止rdtsc
的无序执行,以计时非常短的代码块,或者通过有条件的或间接分支。
另请参阅When should I use _mm_sfence _mm_lfence and _mm_mfence(我的回答和@BeeOnRope的回答),以详细了解lfence
为何无用的原因以及何时使用每个障碍说明。 (或者在我的情况下,当使用C ++而不是asm进行编程时,是C ++内部函数)。
答案 3 :(得分:6)
除了其他答案之外,HotSpot开发人员发现零偏移的lock; addl $0,0(%%esp)
可能不是最佳的,在某些处理器上它可以introduce false data dependencies;相关的jdk bug。
在某些情况下,触摸具有不同偏移量的堆栈位置可以提高性能。
答案 4 :(得分:2)
lock; addl
和xchgl
的重要部分是lock
前缀。它隐含在xchgl
中。这两者之间确实没有区别。我会看看它们是如何组装并选择更短的(以字节为单位),因为对于x86上的等效操作通常更快(因此像xorl eax,eax
这样的技巧)
SSE2的存在可能仅仅是真实条件的代理,它最终是cpuid
的函数。事实证明,SSE2意味着存在lfence
,并且在引导时检查/缓存了SSE2的可用性。 <{1}}在可用时是必需的。