通常您可以确保J 充分对齐(例如自然对齐)。
那么简单的mov 对于纯加载或纯存储就足够了,
在无竞争的情况下比lock-anything 更有效率。
GJ 的回答引用了英特尔手册 re: 对齐的相关部分,与 Why is integer assignment on a naturally aligned variable atomic on x86? 中的相同请注意,在 AMD 上也是原子的公共子集并不像英特尔那样宽容:AMD 可以跨越边界比缓存更窄行,但自然对齐的 8 字节加载/存储在两者上都是安全的。
如果您熟悉 C++11 std::atomic memory_order_acquire / _release 和 seq_cst,请参阅各种 ISA 到 asm 的映射:https://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html。或者在https://godbolt.org/ 上查看x.store(1, std::memory_order_release) 之类的编译器输出
default rel
section .bss
align 4 ; natural alignment
J: resd 1 ; reserve 1 DWORD (NASM syntax)
section .text
mov eax, [J] ; read J (acquire semantics)
mov [J], eax ; write J (release semantics)
;;; seq_cst write J and wait for it to be globally visible before later loads (and stores, but that already happens with mov)
xchg [J], eax ; implicit LOCK prefix, full memory barrier.
seq_cst store could also be done 与 mov [J], eax + mfence,但在大多数 CPU 上通常较慢; GCC 最近转而使用 XCHG,就像其他编译器已经使用了一段时间一样。事实上,MFENCE 是如此slow on Skylake,以至于当您需要与商店分开的屏障时,使用lock or byte [rsp], 0 而不是mfence 会更好。 (atomic_thread_fence(mo_seq_cst))
不幸的是,@GJ 建议的代码的两个部分都不必要地缓慢。
您也不需要 SFENCE,除非您一直在使用像 movntps [mem], xmm0 这样的 NT 商店。 (Does the Intel Memory Model make SFENCE and LFENCE redundant? 是的)。 x86 的内存模型已经是程序顺序 + 带有存储转发的存储缓冲区,因此每个普通加载和普通存储都是 acquire or release operation,并且没有普通存储的 StoreStore 重新排序(到普通内存区域,WB = Write-Back,不是视频 RAM 或其他东西)。
如果您在某些 NT 存储之后存储“数据就绪”标志(即,您希望此存储成为那些 NT 存储的发布操作),并希望您的存储成为release operation wrt。那些较早的 NT 商店,您希望在您的商店之前 SFENCE,以确保看到此商店的读者也会看到此线程的所有早期商店。
在普通存储之后的 SFENCE 只会阻止后来的 NT 存储出现在它之前,但这当然不是通常的问题,即使它确实发生了。
如果您担心其他内核的可见性,请不要担心:store buffer(StoreLoad 重新排序的主要原因)已经尽可能快地将数据提交到 L1d 缓存。像 MFENCE 这样的屏障指令不会让其他内核更快地看到数据,它们只会阻止当前线程稍后的加载/存储操作,直到早期的存储通过正常机制全局可见。
If I don't use fences, how long could it take a core to see another core's writes? 你通常只需要在 x86 上免费的获取/释放语义,不需要顺序一致性。
使用lock cmpxchg 进行加载的唯一原因是您的数据未对齐。但是缓存线分割锁非常很慢,就像锁定所有内核的内存访问,而不是仅仅让当前内核保持一个缓存线的独占所有权 (MESI)。有一个专门针对拆分锁的性能计数器,甚至还有一个最近的 CPU 功能可以使它们出错,因此您可以在不访问硬件性能计数器的情况下在虚拟机中发现此类问题。
如果您不知道您的数据是否对齐,则不能保证 mov 存储是原子的,因此建议这对操作是没有意义的。如果您想要顺序一致性,那么在存储上设置完整的障碍几乎总是更有意义,因为加载更常见并且可能非常便宜。
lock cmpxchg8b 在 32 位 x86 上可用于执行原子 8 字节加载或存储。但仅当您在 486 上时:P5 Pentium 保证对齐的 8 字节加载/存储是原子的,所以在最坏的情况下,您可以使用 x87 fild / fistp 复制到堆栈上的本地。 (假设 x87 FPU 设置为全精度模式,因此它可以无损失地将任何 64 位位模式转换为/从 80 位)。
在较新的 x86 上,即使在 32 位模式下,您也可以假设 movq xmm0, [J] / movd eax, xmm0 / 等或 SSE2 movq 至少为 MMX。这就是gcc -m32 使用的。当然 64 位模式只能使用 64 位整数寄存器。可以使用lock cmpxchg16b 完成 16 字节的原子加载/存储。 (对齐的 SSE 不保证是原子的,尽管在大多数最近的 CPU 上实际上是原子的。但极端情况可能很棘手,例如 Why is integer assignment on a naturally aligned variable atomic on x86? 链接到多插槽 AMD 的示例K10 仅在不同套接字上的内核之间撕裂 8 字节边界。)