TL:DR:因为编译器针对速度进行优化,而不是针对听起来相似的名称。他们也可以采用许多其他可怕的方式来实施它,但他们选择不这样做。
xchg 与 mem 有一个隐含的lock
前缀(在 386 及更高版本上),所以速度非常慢。你总是想避免它,除非你need原子交换,或者正在完全优化代码大小而不关心at all为了性能,如果您确实希望结果与原始值位于同一寄存器中。有时表现为天真(表现不经意)或代码高尔夫球手写的冒泡排序作为交换 2 个内存位置的一部分。
可能clang -Oz
可能会变得那么疯狂,IDK,但希望在这种情况下不会,因为您的 xchg 方式的代码大小较大,需要在两条指令上都有 REX 前缀才能访问 DIL,而 2-mov 方式是 2 字节和 3 -字节指令。clang -Oz
确实做类似的事情push 1
/ pop rax
代替mov eax, 1
节省 2 个字节的代码大小。
GCC -Os
不会使用xchg
对于不需要是原子的交换,因为-Os
仍然关心some关于速度。
另外,我不知道你为什么认为 xchg + dependent mov 会比两个独立的更快或更好的选择mov
可以并行运行的指令。 (存储缓冲区确保加载后存储的顺序正确,无论哪个 uop 首先发现其执行端口空闲)。
See https://agner.org/optimize/以及其他链接https://stackoverflow.com/tags/x86/info
说真的,我只是没有看到任何合理的理由为什么你会认为编译器可能想要使用xchg
,特别是考虑到调用约定不会在 RAX 中传递参数,因此您仍然需要 2 条指令。即使对于寄存器来说,xchg reg,reg
在 Intel CPU 上是 3 uops,它们是无法从 mov-elimination 中受益的微代码 uops。 (一些 AMD CPU 有 2-uopxchg reg,reg
. 为什么 XCHG reg, reg 在现代 Intel 架构上是 3 微操作指令?)
我还猜你正在查看 clang 输出;GCC 将避免部分注册恶作剧(如错误的依赖关系)通过使用movzx eax, byte ptr [rsi]
即使返回值只是低字节,也会加载。零扩展加载比合并到 RAX 的旧值更便宜。所以这是另一个缺点xchg
.