【发布时间】:2018-01-16 19:39:09
【问题描述】:
众所周知,x86-64 指令不支持 64 位立即数(mov 除外)。因此,当将代码从 32 位迁移到 64 位时,如下所示的指令:
cmp rax, addr32
不能替换为以下内容:
cmp rax, addr64
在这种情况下,我正在考虑两种选择:(a) 使用暂存寄存器加载常量或 (b) 使用 rip-relative 寻址。这两种方法如下所示:
mov r11, addr64 ; scratch register
cmp rax, r11
ptr64: dq addr64
...
cmp rax, [rel ptr64] ; encoded as cmp rax, [rip+offset]
我写了一个非常简单的循环来比较两种方法的性能(我粘贴在下面)。虽然(b)使用间接指针,但(a)在指令中编码了立即数(这可能导致 i-cache 的使用更差)。令人惊讶的是,我发现 (b) 的运行速度比 (a) 快约 10%。这个结果在更常见的现实世界代码中是可以预期的吗?
true: dq 0xFFFF0000FFFF0000
false: dq 0xAAAABBBBAAAABBBB
main:
or rax, 1 ; rax is odd and constant "true" is even
mov rcx, 0x1
shl rcx, 30
branch:
mov r11, 0xFFFF0000FFFF0000 ; not present in (b)
cmp rax, r11 ; vs cmp rax, [rel true]
je next
add rax, 2
loop branch
next:
mov rax, 0
ret
【问题讨论】:
-
mov rcx, 0x1/shl rcx, 30比mov ecx, 1<<30具有 零 优势。另外,为什么你希望rax的起始值依赖于 CRT 启动代码留在rax中的任何垃圾? -
是的,我知道,我只是懒惰,不想写一个非常大的常数,然后计数为零:)。至于
rax,我对它的价值不感兴趣,但对永远不会被占用的je next感兴趣。在我看来,具有不可预测的初始值在某种程度上听起来是一个很好的属性,但我想这并不重要,它只是令人困惑。 -
这就是为什么你让汇编器为你生成常量,但字面意思是
mov ecx, 1<<30。这是有效的 NASM 语法:nasm.us/doc/nasmdoc3.html#section-3.5.4。此外,您可以通过使用cmp / jnz作为循环分支来简化这一点。 (并且只需选择您的 64 位常量,以便循环在合理的时间内运行。)循环内的额外未采用分支会对某些 CPU 上的微小循环产生吞吐量影响。 stackoverflow.com/questions/47783926/….
标签: performance assembly x86-64