我看到过x86的INC指令不是原子操作。我的问题是为什么呢?假设我们在x86-64上增加一个64位整数,我们可以使用一条指令完成,因为INC指令适用于内存变量和寄存器。那么为什么它不是原子的呢?
我已经了解这个问题,INC指令本身是原子的,但是如果该操作涉及到多个处理器核心或线程,则可能会发生竞态条件,导致无法保证原子性。因此,需要使用锁或其他同步机制来确保原子性。
现代 x86 处理器作为其执行管道的一部分,将 x86 指令“编译”成一个更低级别的操作集;英特尔称之为 uOps,AMD 称之为 rOps,但它归结为某些类型的单个 x86 指令会被 CPU 中实际的功能单元作为几个步骤执行。
这意味着,例如:
INC EAX
会被执行为一个单独的"mini-op",比如uOp.inc eax
(我称之为这个名字 - 它们不会被公开)。
对于其他的操作数,情况会有所不同,例如:
INC DWORD PTR [ EAX ]
但是底层的分解看起来更像:
uOp.load tmp_reg, [ EAX ]
uOp.inc tmp_reg
uOp.store [ EAX ], tmp_reg
因此,它不是原子执行的。另一方面,如果您通过说LOCK INC [ EAX ]
来进行前缀,那么这将告诉流水线的“编译”阶段以不同的方式分解以确保满足原子性要求。
当然,其他人提到的原因是速度;如果不总是需要使某些内容具备原子性并且必然降低速度,为什么要这样做呢?
lock
的非原子性对于需要同步原语的用例没有任何影响,那么我同意。但我不同意这使得“默认”是原子性 - 在单核上,你只是不需要原子性来进行同步。 - FrankH.inc [mem]
对于同一核上的上下文切换是原子性的:整个指令在引发上下文切换的中断之前或之后执行。如果指令的某些但不是全部uop已经执行,则在进行中断时将取消所有uop,以保留与中断相关的原子性假象。只有MMIO设备或DMA读取可以观察到非原子性,而不是同一CPU上的其他代码。重要的是相对于哪些观察者的原子性,因为逻辑分析仪当然会看到单独的加载/存储(如果未缓存)。 - Peter Cordes来自Agner Fog的软件优化资源:instruction_tables.pdf(1996-2017),你真的不需要保证原子操作,除非你需要它:
带有LOCK前缀的指令具有长延迟,这取决于缓存组织和可能的RAM速度。如果有多个处理器或核心或直接内存访问(DMA)设备,则所有锁定指令都将锁定一个缓存行以进行独占访问,这可能涉及RAM访问。即使在单处理器系统上,LOCK前缀的成本通常也超过一百个时钟周期。对于带有内存操作数的XCHG指令也是如此。
inc
和内存操作数并不是这种操作,至少默认情况下不是。 - harold