用 8086 汇编语言交换 2 个寄存器(16 位)

Cla*_*ian 1 assembly cpu-registers 16-bit x86-16

有人知道如何在不使用其他变量、寄存器、堆栈或任何其他存储位置的情况下交换 2 个寄存器的值吗?谢谢!

就像交换AX,BX。

Pet*_*des 12

8086 对此有一个说明:

xchg   ax, bx
Run Code Online (Sandbox Code Playgroud)

如果您真的需要交换两个 reg,这xchg ax, bx是大多数情况下所有 x86 CPU 上最有效的方法,包括 8086。 -end 效果由于周围代码。或者对于 32 位操作数大小,零延迟mov使带有临时寄存器的 3-mov 序列在 Intel CPU 上更好)。

对于代码大小;xchg-with-ax只需要一个字节。这就是0x90 NOP编码的来源:它是xchg ax, ax,或xchg eax, eax处于 32 位模式1。交换任何其他对寄存器需要 2 个字节进行xchg r, r/m编码。(如果在 64 位模式下需要,则 + REX 前缀。)

在实际的 8086 上,代码提取通常是性能瓶颈,因此xchg迄今为止最好的方法,尤其是使用单字节xchg-with-ax短格式。

脚注1:(在64位模式下,xchg eax, eax将截断RAX为32位,所以0×90是明确地一个nop指令,也是一个xchg)。


对于 32 位 / 64 位寄存器,3 条mov临时指令可以从当前 Intel CPU 上无法实现的mov-elimination中受益xchgxchg在 Intel 上是 3 uops,所有这些都具有 1c 延迟并且需要一个执行单元,因此一个方向具有 2c 延迟,而另一个具有 1c 延迟。请参阅为什么 XCHG reg, reg 是现代英特尔架构上的 3 微操作指令?有关当前 CPU 如何实现它的更多微架构细节。

在 AMD Ryzen 上,xchg在 32/64 位 regs 上是 2 uop 并在重命名阶段处理,所以它就像两个mov并行运行的指令。在早期的 AMD CPU 上,它仍然是 2 uop 指令,但单向有 1c 延迟。


交换添加/子交换或任何其他多指令序列,movxchg寄存器相比毫无意义。它们都有 2 和 3 个周期的延迟,以及更大的代码大小。唯一值得考虑的是mov说明。

或者更好的是,展开循环或重新排列代码以不需要交换,或者只需要一个mov.


用内存交换寄存器

请注意xchgwith memory 有一个隐含的lock前缀。 千万不能使用xchg内存,除非性能一点也不重要,但代码大小一样。(例如在引导加载程序中)。或者,如果您需要它是原子的和/或完整的内存屏障,因为两者兼而有之。

有趣的事实:隐式lock行为在 386 中是新的。在 8086 到 286 上,xchg除非您这样做lock xchg,否则使用 mem 并不特殊,因此您可以有效地使用它。 但现代 CPU 即使在 16 位模式下也将xchg mem, reg其视为相同lock xchg

所以通常最有效的做法是使用另一个寄存器:

     ; emulate  xchg [mem], cx  efficiently for modern x86
   movzx  eax, word [mem]
   mov    [mem], cx
   mov    cx, ax
Run Code Online (Sandbox Code Playgroud)

如果您需要用内存交换寄存器并且没有空闲的临时寄存器,在某些情况下异或交换可能是最佳选择。使用临时内存需要复制内存值(例如到堆栈push [mem],或者在加载+存储内存操作数之前首先将寄存器溢出到第二个临时内存位置。)

迄今为止最低延迟的方式仍然是使用临时寄存器;通常你可以选择一个不在关键路径上的,或者只需要重新加载(首先不保存,因为该值已经在内存中,或者可以使用 ALU 指令从其他寄存器重新计算)。

; spill/reload another register
push  edx            ; save/restore on the stack or anywhere else

movzx edx, word [mem]    ; or just mov dx, [mem]
mov   [mem], ax
mov   eax, edx

pop   edx            ; or better, just clobber a scratch reg
Run Code Online (Sandbox Code Playgroud)

用寄存器交换内存的另外两个合理(但更糟糕)的选项是:

  • 不接触任何其他寄存器(除了SP):

      ; using scratch space on the stack
      push [mem]           ; [mem] can be any addressing mode, e.g. [bx]
      mov  [mem], ax
      pop  ax              ; dep chain = load, store, reload.
    
    Run Code Online (Sandbox Code Playgroud)
  • 或不接触其他任何东西:

      ; using no extra space anywhere
      xor  ax, [mem]
      xor  [mem], ax        ; read-modify-write has store-forwarding + ALU latency
      xor  ax, [mem]        ; dep chain = load+xor, (parallel load)+xor+store, reload+xor
    
    Run Code Online (Sandbox Code Playgroud)

使用两个内存目标xor和一个内存源会降低吞吐量(更多存储和更长的依赖链)。

push/pop版本只适用于可推/弹出操作数大小,但XOR交换适用于任何操作数大小。如果您可以在堆栈上使用临时文件,则保存/恢复版本可能更可取,除非您需要在代码大小和速度之间取得平衡。