正如罗斯的回答所解释的那样,广泛使用的标准方法是溢出(然后重新加载)其他内容以释放 tmp 寄存器。
首先将所有内容加载到寄存器中,而不是根据需要加载,这就是搬起石头砸自己的脚。有时您甚至可以使用 arg 作为内存源操作数,而无需单独的mov
根本没有负载。
但回答一下标题的问题:
尽管有问题标题,我的回答是8086汇编语言中交换2个寄存器(16位)确实有效地解决了寄存器与内存的交换,避免了xchg
因为隐含的lock
字首。溢出(然后重新加载)一个 tmp reg,或者在最坏的情况下,在 reg 和 mem 之间进行异或交换。那是horrible,基本上可以说明为什么您的整个方法会导致实施效率低下。
(正如 Ross 所说,你可能(还)无法编写比编译器更高效的 asm。一旦你了解了如何创建高效的 asm(Agner Fog 的优化指南和微架构指南:https://agner.org/optimize/,以及其他链接https://stackoverflow.com/tags/x86/info)并且可以发现优化编译器输出中的实际低效率,然后您could如果您愿意,有时可以手写更好的汇编。 (通常以编译器输出作为起点)。但通常情况下,如果可能的话,您只需利用这些经验来调整您的 C 源代码,以便从编译器中获得更好的汇编,因为从长远来看,这更有用/可移植。而且它很少重要到值得手写 asm。
此时,您更有可能通过查看来学习提高汇编效率的技术gcc -O3
输出。但错过的优化并不罕见,如果您发现一些优化,您可以在 GCC 的 bugzilla 上报告它们。)
隐含的——lock
的语义xchg
来自386。 The lock
前缀自 8086 起就存在,用于与类似指令一起使用add/or/and/etc [mem], reg or immediate
。包括lock xchg
,这显然没有隐含的lock
行为(即使没有前缀)直到 386。或者可能在那之前没有记录?我不知道英特尔为何做出这样的改变。也许对于原始的 SMP 386 系统来说。
您提到的其他说明是后来添加的: bts
/btr
/btc
在 386 中(但不仅仅用于共享内存,因此隐式lock
没有意义)。
xadd
486年,以及cmpxchg
直到奔腾。 (486 有一个未记录的操作码cmpxchg
, see NASM 附录 A 的旧版本以获得对此的评论)。这些 CPU 的设计晚于 386,大概是在对原始 SMP 系统有一些初步经验之后。
正如你所说,英特尔明智地选择了not make lock
尽管主要用例是多线程代码中的原子操作,但这些新指令是隐式的。 SMP x86 机器开始成为 486 和 Pentium 的一部分,但 UP 机器上的线程之间不需要同步lock
。这是一个相反的问题x86 CMPXCHG 是原子的吗?如果是的话,为什么它需要 LOCK?
8086 是一台单处理器机器,因此对于软件线程之间的同步,简单add [mem], reg
对于中断来说已经是原子的,因此对于上下文切换来说已经是原子的。 (并且不可能同时执行多个线程)。遗产#LOCK
文档仍然提到的外部信号只重要。 DMA 观察器,或用于 MMIO 到设备上的 I/O 寄存器(而不是普通 DRAM)。
(在现代 CPU 上,xchg [mem], reg
在未跨缓存行边界分割的可缓存内存上,仅采用缓存锁,确保该行从加载读取 L1d 到存储提交到 L1d 保持在 MESI Exclusive 或 Modified 状态。)
我不知道为什么 8086 架构师(主要是 Stephen Morse 设计了指令集)选择不制作非原子xchg
有可用内存。 (更正,我认为他做到了,而且只是 386 改变了它;这个答案最初是在我知道这是 386 改变之前写的。)也许在 8086 上,让 CPU 断言并没有慢多少#LOCK
在进行存储+加载事务时?但随后我们就陷入了 x86 其余部分的这些语义。 x86 设计很少具有前瞻性,如果主要用例xchg
用于原子 I/O 然后它节省了代码大小lock
隐含的。
没有办法禁用隐式锁xchg [mem], reg
您需要使用多个不同的指令。异或交换是可能的,但效率很低。也许还没有那么糟糕xchg
,取决于微架构和周围的代码(在执行任何后续加载之前等待所有先前的存储执行并提交到 L1d 缓存是多么糟糕)。例如与内存目标相比,一些运行中的缓存未命中存储可能会变得非常昂贵xor
它可以将数据留在存储缓冲区中。
编译器基本上不会使用xchg
即使在寄存器之间(因为不比3便宜mov英特尔的说明,所以它通常不是一个有用的窥视孔优化来寻找)。他们仅用它来实现std::atomic
商店有seq_cst
内存顺序(因为它比mov
+ mfence
在大多数 uarch 上:为什么具有顺序一致性的 std::atomic 存储使用 XCHG?),并实施std::atomic::exchange
. But not std::swap与 reg 或内存。
如果 x86 有非原子 2 或 3 uop,它有时会很有用swap reg,mem
,但事实并非如此。没有这样的指令。
但特别是对于具有 16 个寄存器的 x86-64,您只会遇到这个问题,因为您自己创建了它。给自己留一些临时的计算规则。