Instruction
CMPXCHG
Compares the accumulator with the destination: if equal, stores the source there and sets ZF, else loads the destination; atomic with LOCK.
- Extensions
- Forms
- 6 (6 Legacy)
- Data
- Intel XED v2026.08.23
With a LOCK prefix and a memory destination, CMPXCHG is the
compare-and-swap of x86. GCC 14 and Clang 23 at -O2 compile C’s
atomic_compare_exchange_strong to it, and atomic read-modify-writes
that have no instruction of their own, such as a fetch-and-OR whose old
value is used, to this loop:
- Load the shared value into the accumulator (EAX or RAX).
- Compute the new value from it in another register.
- Execute LOCK CMPXCHG with the shared location as the destination and the new value as the source. If the location still holds what step 1 loaded, the new value is stored and ZF is set.
- If ZF is clear, another thread changed the value in between. The accumulator now holds that value, so the loop goes back to step 2 without loading it again.
Taking a spinlock is the shortest case: 0 in EAX, 1 in the source register, and the lock is taken when ZF is set.
Points that are easy to miss:
- Only the LOCK form is atomic with respect to other processors, and LOCK needs a memory destination (#UD otherwise).
- The destination is written even when the comparison fails: Intel’s and AMD’s manuals both say the processor writes back the value it read. CMPXCHG therefore needs write access to the page either way.
- With 32-bit operands the outcome decides which register is written. On success EAX is not written, so bits 63:32 of RAX keep their old value; on failure EAX is loaded and bits 63:32 are cleared. A register destination is the other way round: written and zero-extended on success, left whole on failure. Zen 5 and the Intel processor that ran the cases of the Operation section agree.
- A locked operand that crosses a cache-line boundary makes the processor lock the bus, which system software can turn into #AC with split-lock disable (Intel’s Volume 3A, Section 11.1.2.3). Intel’s manual recommends naturally aligned locked operands, and AMD’s optimization guide for Zen 5 ones that do not cross a 64-byte boundary.
For two registers’ worth of data, such as a pointer with a version counter against the ABA problem, use CMPXCHG8B or CMPXCHG16B. XADD and XCHG do fetch-and-add and swap in one step, without a loop, and CMPccXADD adds only when a condition holds.
In the optimization guide
What the optimization guide says about CMPXCHG, a line per claim. Each links to the claim in the guide, with its sources and where they differ.
Memory
- Sharing between cores: A contended lock is best taken by spinning on a plain load with PAUSE and trying
cmpxchgonly when it looks free, with backoff.
Forms
| Instruction | Encoding | Requires |
|---|---|---|
CMPXCHG m8, r8
CMPXCHG_MEMb_GPR8 | 0F B0 /r | Base instruction set |
CMPXCHG r8, r8
CMPXCHG_GPR8_GPR8 | 0F B0 /r | Base instruction set |
CMPXCHG m16/32/64, r16/32/64
CMPXCHG_MEMv_GPRv | 0F B1 /r | Base instruction set |
CMPXCHG r16/32/64, r16/32/64
CMPXCHG_GPRv_GPRv | 0F B1 /r | Base instruction set |
LOCK CMPXCHG m8, r8
CMPXCHG_LOCK_MEMb_GPR8 | 0F B0 /r | Base instruction set |
LOCK CMPXCHG m16/32/64, r16/32/64
CMPXCHG_LOCK_MEMv_GPRv | 0F B1 /r | Base instruction set |
Latency and throughput
| Model | m8, r8 | r8, r8 | m16, r16m32, r32m64, r64 | r16, r16r32, r32r64, r64 | CMPXCHG m8, r8 | CMPXCHG m16, r16CMPXCHG m32, r32CMPXCHG m64, r64 |
|---|---|---|---|---|---|---|
| Sandy Bridge | 8 / 2p015 | 5 / 1.33p015 | 8 / 2p015 | 5 / 1.33p015 | 8 / 2p015 | 8 / 2p015 |
| Haswell | 9 / 1p0156 | 5 / 1.25p0156 | 9 / 1p0156 | 5 / 1.25p0156 | 9 / 1p0156 | 9 / 1p0156 |
| Broadwell, Skylake, Skylake-SP, Ice Lake | 8 / 1p0156 | 5 / 1.25p0156 | 8 / 1p0156 | 5 / 1.25p0156 | 8 / 1p0156 | 8 / 1p0156 |
| Alder Lake, Sapphire Rapids | 13 / 1p0 p1 p5 p6 p11 | 3 / 1p0 p1 p5 p6 p11 | 12 / 1p0 p1 p5 p6 p11 | 3 / 1p0 p1 p5 p6 p11 | 13 / 1p0 p1 p5 p6 p11 | 12 / 1p0 p1 p5 p6 p11 |
| Lunar Lake | 13 / 0.67p012345 | 3 / 0.83p012345 | 12 / 0.67p012345 | 3 / 0.83p012345 | 13 / 0.67p012345 | 12 / 0.67p012345 |
| Bonnell | 6 / 3p1 | 9 / 4.5p01 | 14 / 7p1 | 15 / 7.5p01 | 6 / 3p1 | 14 / 7p1 |
| Silvermont | 10 / 3IEC0 IEC1 MEC | 6 / 3IEC0 IEC1 | 10 / 3IEC0 IEC1 MEC | 6 / 3IEC0 IEC1 | 10 / 3IEC0 IEC1 MEC | 10 / 3IEC0 IEC1 MEC |
| Piledriver | 3 / 23EX1 | 3 / 3EX1 | 3 / 21EX1 | 3 / 3EX1 | 3 / 23EX1 | 3 / 21EX1 |
| Zen | 8 / 0.5ALU0 ALU1 ALU2 ALU3 | 1 / 0.25ALU0 ALU1 ALU2 ALU3 | 8 / 0.5ALU0 ALU1 ALU2 ALU3 | 1 / 0.25ALU0 ALU1 ALU2 ALU3 | 8 / 0.5ALU0 ALU1 ALU2 ALU3 | 8 / 0.5ALU0 ALU1 ALU2 ALU3 |
| Zen 2 | 8 / 0.33ALU0 ALU1 ALU2 ALU3 | 3 / 0.25ALU0 ALU1 ALU2 ALU3 | 8 / 0.33ALU0 ALU1 ALU2 ALU3 | 3 / 0.25ALU0 ALU1 ALU2 ALU3 | 8 / 0.33ALU0 ALU1 ALU2 ALU3 | 8 / 0.33ALU0 ALU1 ALU2 ALU3 |
| Zen 3, Zen 4 | 7 / 3ALU0 ALU1 ALU2 ALU3 | 3 / 3ALU0 ALU1 ALU2 ALU3 | 3 / 3ALU0 ALU1 ALU2 ALU3 | 3 / 3ALU0 ALU1 ALU2 ALU3 | 7 / 3ALU0 ALU1 ALU2 ALU3 | 3 / 3ALU0 ALU1 ALU2 ALU3 |
| Jaguar | 11 / 1.5ALU0 ALU1 | 3 / 1.5ALU0 ALU1 | 11 / 1.5ALU0 ALU1 | 3 / 1.5ALU0 ALU1 | 16 / 16ALU0 ALU1 | 17 / 17ALU0 ALU1 |
Operation
What the forms do, in pseudocode that our tests run against results recorded on real processors. How to read.
old = dest.read() # with LOCK: one atomic read-modify-write
flags = sub_flags(rAX, old) # as CMP rAX, old
if rAX == old:
dest.write(src) # a 32-bit register: bits 63:32 cleared
else:
rAX = old # EAX: bits 63:32 cleared
if dest.is_memory:
dest.write(old) # memory is written back all the same
# a register destination is not written: bits 63:32 of a 32-bit
# one stay as they wereRun on AMD Zen 5 (EPYC 9645) and an Intel processor under KVM (model hidden): 272 cases.
Operands
| Operand | Kind | Action |
|---|---|---|
CMPXCHG r/m8, r8 CMPXCHG r/m16/32/64, r16/32/64 LOCK CMPXCHG m8, r8 LOCK CMPXCHG m16/32/64, r16/32/64 | ||
r/m8/16/32/64 | explicit | read, conditional write |
r8/16/32/64 | explicit | read |
AL, rAX | implicit | read, conditional write |
RFLAGS | implicit | write |
Flags
| OF | SF | ZF | AF | PF | CF |
|---|---|---|---|---|---|
| All forms | |||||
| M | M | M | M | M | M |
Attributes
What else XED records about the forms. How to read.
- Memory forms without LOCK
- accepts a LOCK prefix
- LOCK forms
- locked: atomic read-modify-write; accepts XACQUIRE; accepts XRELEASE
CPUID and processors
Each form belongs to an XED ISA set. A form can be used when the processor reports every CPUID bit of one of its ISA set's alternatives.
| ISA set | CPUID | Processors |
|---|---|---|
I486REAL | No CPUID bit: part of the base instruction set. | Every x86-64 processor in XED’s model and Zen 3 to Zen 6 |
More General-purpose instructions
Sources
- Intel XED v2026.08.23 (commit
0bcb6237345c): forms, encodings, operands, flags, exception classes, attributes, ISA sets, CPUID bits and chip model. - The CPU data of x86-64.net: Zen 3 to Zen 6, from GCC’s
-marchfeature sets and AMD’s Processor Programming References, which the page of each processor cites. - LLVM 23.1.2's x86 scheduling models (commit
85ac56026243): the latency, reciprocal throughput and ports of each form, as llvm-mca reports them. - Intel 64 and IA-32 Architectures Software Developer's Manual, Volume 2 (325383-093, September 2026): Volume 2A, page 3-194 (CMPXCHG)
- Intel 64 and IA-32 Architectures Software Developer's Manual, Volume 3 (325384-093, September 2026)
- AMD64 Architecture Programmer's Manual, Volume 3: General-Purpose and System Instructions (24594, rev. 3.39, October 2026)
- Software Optimization Guide for the AMD Zen5 Microarchitecture (58455, rev. 1.00, August 2024), Wayback Machine copy of AMD's ZIP
The tables are derived from Intel XED, Copyright Intel Corporation, licensed under the Apache License 2.0; x86-64.net converted and reformatted them. The timings come from LLVM's scheduling models, part of the LLVM Project, licensed under the Apache License 2.0 with LLVM Exceptions. The text is our own.