在 64 位 x86 asm 中,您可以使用整数 mov rax, [rsi],或者 x87 或 SSE2。 As long as the address is 8-byte aligned (or on Intel P6 and later CPUs: doesn't cross a cache-line boundary) the load or store will be atomic.
请注意,AMD 和 Intel 的共同基线仍然只有 8 字节对齐; 仅英特尔保证未对齐但未跨缓存行拆分的可缓存负载的原子性。 (AMD 可能会保证一些更宽的边界,或者至少在实践中对一些后来的 CPU 这样做)。
在 32 位 x86 asm 中,仅使用整数寄存器的唯一选择是 lock cmpxchg8b,但这对于纯加载或纯存储来说很糟糕。 (您可以通过设置 expected=desired = 0 将其用作负载,但只读内存除外)。 (gcc/clang 在 64 位模式下将 lock cmpxchg16b 用于 atomic<struct_16_bytes>,但一些编译器只是选择使 16 字节对象不是无锁的。)
所以答案是:不要使用整数 regs:fild qword / fistp qword 可以复制任何位模式而无需更改它。 (只要 x87 精度控制设置为全 64 位尾数)。这对于 Pentium 及更高版本上的对齐地址是原子的。
在现代 x86 上,使用 SSE2 movq 加载或存储。例如
; atomically store edx:eax to qword [edi], assuming [edi] is 8-byte aligned
movd xmm0, eax
pinsrd xmm0, edx ; SSE4.1
movq [edi], xmm0
只有 SSE1 可用,请使用 movlps。 (对于负载,您可能希望使用 xorps 打破对 xmm 寄存器旧值的错误依赖)。
使用 MMX,movq 与 mm0-7 之间可以正常工作。
gcc 使用 SSE2 movq、SSE1 movlps 或 x87 fild/fstp,在 32 位模式下使用 std::atomic<int64_t> 的优先顺序。即使 SSE2 可用,Clang -m32 也不幸使用 lock cmpxchg8b:LLVM bug 33109。 .
某些版本的 gcc 被配置为默认开启 -msse2,即使使用 -m32(在这种情况下,您可以使用 -mno-sse2 或 -march=i486 来查看 gcc 在没有它的情况下会做什么)。
我输入load and store functions on the Godbolt compiler explorer 以查看来自 gcc 的带有 x87、SSE 和 SSE2 的 asm。并且来自 clang4.0.1 和 ICC18。
gcc 作为 int->xmm 或 xmm->int 的一部分在内存中反弹,即使 SSE4 (pinsrd / pextrd) 可用。这是一个错过的优化 (gcc bug 80833)。在 64 位模式下,它支持带有 -mtune=intel 或 -mtune=haswell 的 ALU movd + pinsrd / pextrd,但显然不是在 32 位模式下或不适用于此用例(XMM 中的 64 位整数而不是正确的矢量化)。无论如何,请记住只有来自atomic<long long> shared 的加载或存储必须是原子的,堆栈的其他加载/存储是私有的。
在 MSVC 中,在更高版本的 Visual C++ 2019 中有一个 __iso_volatile_load64 内部函数,可以编译为适当的指令序列。