Linux内核lock; addl $0,0(%%esp)
用作写屏障,而RE2库xchgl (%0),%0
用作写屏障.有什么区别,哪个更好?
x86还需要读屏障指令吗?RE2将其读屏障功能定义为x86上的无操作,而Linux lfence
根据SSE2是否可用将其定义为无操作或无操作.什么时候lfence
需要?
引用IA32手册(第3A卷,第8.2章:内存排序):
在用于内存区域的单处理器系统中,内存区域定义为可写回缓存,内存排序模型遵循以下原则[...]
读取不会与其他读取重新排序
写入不会与较旧的读取重新排序
写入内存不会与其他写入重新排序,但
用
CLFLUSH
指令执行写入使用非时间移动指令执行的流存储(写入)([此处的指令列表])
字符串操作(参见第8.2.4.1节)
读取可以使用较旧的写入到不同位置进行重新排序,但不能使用较旧的写入到同一位置.
无法使用I/O指令,锁定指令或序列化指令对读取或写入进行重新排序
读取不能通过
LFENCE
和MFENCE
说明写不能通过
SFENCE
和MFENCE
指示
注意:上面的"在单处理器系统中"有点误导.每个(逻辑)处理器都有相同的规则; 然后,手册继续描述多个处理器之间的附加排序规则.与此问题有关的唯一一点是
锁定的指令有一个总订单.
简而言之,只要你写回写内存(只要你不是驱动程序或图形程序员,你就会看到所有内存),大多数x86指令几乎是顺序一致的 - 唯一的重新排序x86 CPU可以执行的是稍后重新排序(独立)读取以在写入之前执行.关于写入障碍的主要问题是它们具有lock
前缀(隐式或显式),禁止所有重新排序并确保多处理器系统中的所有处理器以相同的顺序看到操作.
此外,在回写存储器中,读取永远不会重新排序,因此不需要读取障碍.最近的x86处理器具有较弱的内存一致性模型,适用于流存储和写入组合内存(通常用于映射图形内存).这就是各种各样的地方fence
说明发挥作用; 它们对于任何其他内存类型都不是必需的,但Linux内核中的某些驱动程序确实处理了写入组合内存,因此它们只是以这种方式定义了它们的读取障碍.每种存储器类型的排序模型列表在第11.3.1节中.IA-32手册的3A.短版本:Write-Through,Write-Back和Write-Protected允许推测性读取(遵循上面详述的规则),Uncachable和Strong Uncacheable内存具有强大的排序保证(没有处理器重新排序,读取/写入立即执行,用于MMIO并且写入组合内存具有弱排序(即,需要围栏的宽松排序规则).
如果我们在(%% esp)地址测试锁定变量的0状态,则" lock; addl $ 0,0(%% esp) "会更快.因为我们向lock变量添加0值,如果地址(%% esp)的变量的锁定值为0,则将zero标志设置为1.
英特尔数据表中的lfence:
对LFENCE指令之前发出的所有内存加载指令执行序列化操作.此序列化操作保证在LFENCE指令之后的任何加载指令全局可见之前,LFENCE指令在程序顺序之前的每个加载指令都是全局可见的.
例如:如果正确对齐,像'mov'这样的内存写指令是原子的(它们不需要锁前缀).但是此指令通常在CPU缓存中执行,此时对于所有其他线程不会全局可见,因为必须首先执行内存栅栏.
编辑:
因此,这两条指令之间的主要区别在于xchgl指令对条件标志没有任何影响.当然我们可以使用lock cmpxchg指令测试锁变量状态,但这仍然比使用lock add $ 0指令更复杂.
lock addl $0, (%esp)
是替代品mfence
,不是lfence
。
用例是当您需要阻止StoreLoad重新排序(x86的强内存模型允许的唯一类型),但是不需要对共享变量进行原子RMW操作时。 https://preshing.com/20120515/memory-reordering-caught-in-the-act/
例如,假设对齐std::atomic
:
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 在seq_cst存储中使用mov
+ mfence
)。
使用lock addl $0, (%esp)
的一个障碍。任何lock
ed指令都是完全障碍。 锁xchg的行为与mfence相同吗?
(或者到其他位置,但是堆栈在L1d中几乎总是私有且很热,因此它是一个不错的选择。但是,这可能会使用堆栈底部的数据为某些对象创建依赖链。)
您只能xchg
通过将其折叠到存储中来用作屏障,因为它会无条件地使用不依赖于旧值的值写入内存位置。
如果可能,最好使用xchg
seq-cst存储,即使它也从共享位置读取。 mfence
速度比最近的Intel CPU慢(加载并存储了唯一需要重新排序的指令吗?),并且也以相同的方式阻止了独立非内存指令的无序执行lfence
。
它甚至可能值得使用,lock addl $0, (%esp)/(%rsp)
而不是mfence
在mfence
可用时使用,但我还没有尝试过不利之处。使用-64(%rsp)
或某些东西可能不太可能延长对某些热点(本地地址或返回地址)的数据依赖,但这会使诸如valgrind之类的工具不满意。
lfence
除非您要从具有MOVNTDQA负载的视频RAM(或其他一些WC弱排序区域)中读取数据,否则它对于内存排序永远不会有用。
序列化无序执行(而不是存储缓冲区)对于停止StoreLoad重新排序(x86的强大内存模型允许普通WB(回写)内存区域的唯一类型)无用。
现实的用例用于lfence
阻止rdtsc
对非常短的代码块计时的乱序执行,或者用于通过有条件或间接分支阻止推测来缓解Spectre缓解。
另请参阅何时应该使用_mm_sfence _mm_lfence和_mm_mfence(我的答案和@BeeOnRope的答案)以获取更多关于为什么lfence
无效的信息以及何时使用每个障碍说明的更多信息。(或者在我的情况下,使用C ++而不是asm进行编程时是C ++内部函数)。
除了其他答案之外,HotSpot开发人员发现lock; addl $0,0(%%esp)
零偏移可能不是最佳的,在某些处理器上它可能会引入错误的数据依赖性 ; 相关的jdk bug.
触摸具有不同偏移的堆栈位置可以在某些情况下提高性能.