Performance of xchg reg,reg

In compilers, in particular in SSA destruction, or, more generally, register allocation, you sometimes have situations where you want to swap the contents of registers, e.g., when r1 contains a and r2 contains b on one path, while r1 contains b and r2 contains a on the other path, and both paths join. This can be achieved with 3 mov instructions, but requires an extra register or memory location. The AMD64 instruction set contains the instruction xchg, which swaps the registers without extra location.

More generally, there may be more than two registers whose values have to be moved in a cyclic way. This also requires an extra register or memory location and (for an n-register cycle) n+1 mov instructions if performed using mov instructions, or it can be achieved with n-1 xchg instructions.

Mov instructions are supported very well in recent CPU cores, with the cores usually being able to perform as many independent (and often even dependent) moves as the register renamer accepts instructions (e.g., if the register renamer accepts 8 instructions per cycle, 8 moves per cycle can be performed, usually even dependent moves).

But how do xchg instructions perform? Should a register allocator use them where they are convenient, or prefer to use mov instructions and an extra register?

This page contains microbenchmarks and presents results for them on various cores from Intel and AMD. The first set of microbenchmarks perform 1G xchg instructions in a loop containing 28 xchg instructions (plus two instructions for the loop).

One (independent) contains a sequence of 7 xchg instructions that are fully independent of each other, all accessing different registers, followed by the same sequence another three times. So the xchg instructions are as independent as possible with 14 registers involved.

The other (dependent) contains a sequence of 14 xchg instructions where each subsequent instruction uses one register of the previous instruction, implementing a 15-register cycle. This sequence is followed by the reversal of this sequence, resulting in performing the 15-register cycle in the other direction.

Here are the results in Gcycles on recent microarchitectures, i.e., a result of 0.25 means that the microarchitecture performs 4 xchg instructions per cycle. Smaller is better.

  indep dep  core        family  CPU 
  0.26  0.26 Zen5        AMD     Ryzen AI Pro 350
  0.35  0.35 Zen4        AMD     Ryzen 8700G
  0.35  0.35 Zen3        AMD     Ryzen 5800X
  0.61  1.50 Lion Cove   Intel P Core Ultra 225
  1.00  1.50 Golden Cove Intel P Core i3-1315U
  0.82  1.50 Rocket Lake Intel P Xeon E-2388G
  2.07  2.05 Skymont     Intel E Core Ultra 225
  3.45  3.45 Gracemont   Intel E Core i3-1315U

Given the results of the dependent benchmark, it looks like the AMD processors perform xchg in the register renamer, consuming the resources of two ordinary instructions there (probably because two result registers are involved).

On Intel microarchitectures, whether P-cores or E-cores, but especially the latter, xchg seems to have gotten little love.

Let's see how this affects cyclic moves between registers. The following microbenchmarks perform cyclic moves through 2-5 registers (cyc2-cyc5) in three different ways:

mem
Store the first register to memory, then perform n-1 moves among registers, then load the memory location into the final register.
reg
Move the first register into a spare register, then perform n-1 moves among the cycled registers, then move the spare register into the final register.
xchg
Use n-1 xchg instructions.
The benchmark loop contains 12 cycles, and cycles involving non-overlapping sets of registers are benchmarked (6 sets for cyc2, 4 for cyc3, 3 for cyc4, 2 for cyc5). At least three different memory locations are used for holding the value of the first register in memory, but the same register is used as the spare register. Here are the results (execution cycles per cyclic move (i.e., n+1 movs in case of reg, n-1 xchgs in case of xchg).
    cyc2      |    cyc3      |    cyc4       |    cyc5       |core        family  CPU
mem  reg  xchg|mem  reg  xchg|mem  reg   xchg|mem  reg   xchg| 
0.50 0.50 0.27|0.67 0.58 0.52|0.83 0.83  0.77|1.08 0.83  1.02|Zen5        AMD     Ryzen AI Pro 350
1.00 0.51 0.36|1.00 0.68 0.69|1.00 0.85  1.03|1.01 1.01  1.36|Zen4        AMD     Ryzen 8700G
1.00 0.51 0.42|1.00 0.68 0.70|1.00 0.92  1.03|1.02 1.01  1.36|Zen3        AMD     Ryzen 5800X
0.50 0.40 0.68|0.54 0.56 1.28|0.67 0.66  1.97|0.81 0.81  4.21|Lion Cove   Intel P Core Ultra 225
0.61 0.53 1.00|0.70 0.70 2.00|0.89 0.90  3.00|1.06 1.08  4.41|Golden Cove Intel P Core i3-1315U 
0.82 0.67 1.00|0.87 0.83 2.00|1.08 1.08  3.00|1.22 1.22  4.66|Rocket Lake Intel P Xeon E-2388G 
1.00 0.40 1.45|0.65 0.73 4.14|0.65 0.65  7.35|0.78 0.86  9.44|Skymont     Intel E Core Ultra 225 
0.67 0.67 3.08|0.83 0.83 6.73|1.03 1.03 10.63|1.23 1.23 13.77|Gracemont   Intel E Core i3-1315U
On Zen5, even a 5-register cycle can be profitably implemented with xchg when you have no spare register, a 4-register cycle even when you have a spare register. For Zen4 and Zen3 cyc4 (for mem) and cyc3 (for reg) are borderline cases, and for shorter cycles xchg is better.

On Intel processors, xchg is so slow that it never is profitable to use it. The slowness takes on extreme values in the E-cores, with slowdowns by a factor >10 for cyc4 and cyc5.


Anton Ertl
[ICO]NameLast modifiedSizeDescription

[PARENTDIR]Parent Directory  -  
[   ]cyc5xchg2026-10-13 09:53 16K 
[   ]cyc5reg2026-10-13 09:53 16K 
[   ]cyc52026-10-13 09:53 16K 
[   ]cyc4xchg2026-10-13 09:53 16K 
[   ]cyc4reg2026-10-13 09:53 16K 
[   ]cyc42026-10-13 09:53 16K 
[   ]cyc3xchg2026-10-13 09:53 16K 
[   ]cyc3reg2026-10-13 09:53 16K 
[   ]cyc32026-10-13 09:53 16K 
[   ]cyc2xchg2026-10-13 09:53 16K 
[   ]cyc2reg2026-10-13 09:53 16K 
[   ]cyc22026-10-13 09:53 16K 
[   ]independent2026-10-12 09:41 16K 
[   ]dependent2026-10-12 09:41 16K 
[   ]bench2026-10-12 08:54 16K 
[   ]cyc5.s2026-10-13 09:53 1.1K 
[   ]cyc5reg.s2026-10-13 09:53 1.0K 
[   ]independent.s2026-10-12 08:49 1.0K 
[   ]dependent.s2026-10-12 09:10 1.0K 
[   ]cyc4.s2026-10-13 09:53 1.0K 
[   ]cyc5xchg.s2026-10-13 09:53 1.0K 
[   ]cyc4reg.s2026-10-13 09:53 960  
[   ]cyc3.s2026-10-13 09:53 923  
[   ]cyc4xchg.s2026-10-13 09:53 919  
[   ]Makefile2026-10-13 10:42 864  
[   ]cyc3reg.s2026-10-13 09:53 863  
[   ]cyc2.s2026-10-13 09:53 826  
[   ]cyc3xchg.s2026-10-13 09:53 821  
[   ]cyc2reg.s2026-10-13 09:53 766  
[   ]cyc2xchg.s2026-10-13 09:53 723  
[TXT]main.c2026-10-13 09:53 133  

Apache/2.4.67 (Debian) OpenSSL/3.0.18 Server at www.complang.tuwien.ac.at Port 80