序列化乱序执行(但不是存储缓冲区)对于停止StoreLoad重新排序(x86的强内存模型允许正常WB(回写)内存区域的唯一类型)没有用。 lfence的实际用例是阻止rdtsc的无序执行,以便为非常短的代码块计时,或者通过条件或间接分支阻止推测来缓解Spectre。 另请参见When should I use _mm_sfence _mm_lfence and _mm_mfence(我的答案和@BeeOnRope的答案),以了解为什么lfence没有用,以及何时使用每一条barrier指令(或者在我的答案中,当用C而不是asm编程时,使用C intrinsic)。
5条答案
按热度按时间093gszye1#
引自IA 32手册(第3A卷,第8.2章:内存排序):
在单处理器系统中,对于定义为可写回缓存的内存区域,内存排序模型遵循以下原则[...]
CLFLUSH
指令执行的写入LFENCE
和MFENCE
指令SFENCE
和MFENCE
指令注意:上面的“在单处理器系统中”有点误导。相同的规则适用于每个(逻辑)处理器;手册接着描述了多个处理器之间的附加排序规则。2关于这个问题的唯一一点是
简而言之,只要你写回写内存(只要您不是驱动程序员或图形程序员,就可以看到所有内存),大多数x86指令几乎是顺序一致的-x86 CPU唯一能执行的重新排序是以后重新排序在写入之前执行的(独立)读取。写屏障的主要特点是它们具有
lock
前缀(隐式或显式),其禁止所有重新排序,并确保操作被多处理器系统中的所有处理器以相同的顺序看到。此外,在回写内存中,读取永远不会重新排序,因此不需要读屏障。最近的x86处理器有一个较弱的内存一致性模型,用于流存储和写组合内存(通常用于Map图形内存)。这就是各种
fence
指令发挥作用的地方;对于任何其他内存类型,它们都不是必需的,但Linux内核中的某些驱动程序确实处理组合写入内存,因此它们只是以这种方式定义其读取屏障。IA-32手册第3A卷第11.3.1节中列出了每种内存类型的排序模型。简短版本:直写、回写和写保护允许推测性读取(遵循如上详述的规则),不可缓存和强不可缓存存储器具有强排序保证(没有处理器重新排序,立即执行读取/写入,用于MMIO),并且写入组合存储器具有弱排序(即,需要围栏的宽松排序规则)。kx7yvsdv2#
"锁;addl $0,0(%% esp)"在测试(%% esp)地址处的锁定变量的0状态时速度更快。因为我们将0值添加到锁定变量,并且如果地址(%% esp)处的变量的锁定值为0,则零标志将设置为1。
对在LFENCE指令之前发出的所有从内存加载指令执行序列化操作。此序列化操作保证在程序顺序中LFENCE指令之前的每个加载指令在LFENCE指令之后的任何加载指令全局可见之前都是全局可见的。
(编者注:
mfence
或lock
ed操作是实现顺序一致性的唯一有用的隔离(在存储之后)。lfence
* 不会 * 阻止存储缓冲区重新排序StoreLoad。)例如:内存写入指令如"mov"如果正确对齐,则是原子的(它们不需要锁前缀)。但此指令通常在CPU缓存中执行,此时不会对所有其他线程全局可见,因为必须首先执行内存围栏,以使此线程等待,直到其他线程可以看到以前的存储。
因此,这两条指令的主要区别在于xchgl指令不会对条件标志产生任何影响。当然,我们可以使用lock cmpxchg指令测试锁变量状态,但这仍然比使用lock add $0指令复杂。
daupos2t3#
lock addl $0, (%esp)
是mfence
的替代物,而不是lfence
。(
lock add
在现代CPU上通常速度更快,尤其是Intel Skylake,它更新了微码wheremfence
acts likelfence
as well,阻止了乱序执行,甚至寄存器上的指令。这就是为什么GCC最近在需要完整屏障时改用虚拟lock add
而不是mfence
。)用例是当您需要阻止StoreLoad重新排序(x86的强内存模型允许的唯一类型),但不需要对共享变量执行原子RMW操作时。https://preshing.com/20120515/memory-reordering-caught-in-the-act/
例如,假设对准
std::atomic<int> a,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?(Or到其他位置,但是栈在L1d中几乎总是私有的和热的,所以这是一个不错的选择。但是这可能会使用栈底部的数据创建一个依赖链。
您只能通过将
xchg
折叠到存储中来将其用作屏障,因为它无条件地向内存位置写入一个不依赖于旧值的值。在可能的情况下,使用
xchg
进行seq-cst存储可能是最好的,即使它也从共享位置读取。mfence
在最新的Intel CPU上比预期的要慢(加载和存储是唯一被重新排序的指令吗?),而且还像lfence
一样阻止了独立的非内存指令的无序执行。即使
mfence
可用,使用lock addl $0, (%esp)/(%rsp)
代替mfence
也是值得的,但我还没有体验过它的缺点,使用-64(%rsp)
或其他东西可能会使它不太可能延长对热点(本地或返回地址)的数据依赖,但这会让valgrind这样的工具不高兴。lfence
对于内存排序毫无用处,除非您使用MOVNTDQA加载从视频RAM(或其他WC弱排序区域)读取。**序列化乱序执行(但不是存储缓冲区)对于停止StoreLoad重新排序(x86的强内存模型允许正常WB(回写)内存区域的唯一类型)没有用。
lfence
的实际用例是阻止rdtsc
的无序执行,以便为非常短的代码块计时,或者通过条件或间接分支阻止推测来缓解Spectre。另请参见When should I use _mm_sfence _mm_lfence and _mm_mfence(我的答案和@BeeOnRope的答案),以了解为什么
lfence
没有用,以及何时使用每一条barrier指令(或者在我的答案中,当用C而不是asm编程时,使用C intrinsic)。pbwdgjma4#
除了其他答案,HotSpot开发人员发现零偏移的
lock; addl $0,0(%%esp)
可能不是最佳的,在一些处理器上它可以introduce false data dependencies;相关的jdk bug。在某些情况下,使用不同的偏移量接触堆栈位置可以提高性能。
9w11ddsr5#
lock; addl
和xchgl
的重要部分是lock
前缀。它对于xchgl
是隐式的。两者之间实际上没有区别。我将查看它们是如何组装的,并选择较短的一个(以字节为单位),因为这对于x86上的等效操作来说通常更快(因此有xorl eax,eax
这样的技巧)SSE 2的存在可能只是真实的情况的代理,实际情况最终是
cpuid
的函数。结果可能是SSE 2暗示lfence
的存在,并且在 Boot 时检查/缓存了SSE 2的可用性。lfence
可用时需要它。