与mov reg,imm64相比,RIP相对寻址的执行情况如何?

时间:2018-01-16 19:39:10

标签: performance assembly x86-64

众所周知,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

1 个答案:

答案 0 :(得分:3)

  

令人惊讶的是,我发现(b)比(a)

快〜10%

您可能在AMD Bulldozer系列或Ryzen以外的CPU上进行了测试,这些CPU具有快速loop指令。在其他CPU上, loop is very slow, mostly on purpose for historical reasons,因此您遇到了瓶颈。例如7个uops,Haswell每5c吞吐量一个。

mov r64, imm64对于uop缓存吞吐量不利,因为在英特尔的uop缓存中立即占用2个插槽。 (请参阅Agner Fog's microarch pdf中的Sandybridge uop缓存部分)和Which is faster, imm64 or m64 for x86-64?,其中列出了详细信息。

即使除此之外,在循环中增加1个uop使其运行速度变慢也不足为奇。您可能不在AMD CPU上(单个uop /每2个时钟1个loop),因为如此微小的循环中的额外mov将产生超过10%的差异。或者完全没有区别,因为它每2个时钟只有3个对4个uop,如果这是正确的,甚至很小的loop循环限制为每2个时钟跳一次。

在英特尔上,loop为7 uop,大多数CPU每5个时钟吞吐量一个,因此每时钟4个问题/重命名瓶颈不会成为您的目标。 loop是微编码的,因此前端不能从循环缓冲区运行。 (并且Skylake CPU通过微码更新禁用其LSD以修复部分寄存器错误。)因此每次循环时都必须从uop缓存中重新读取mov r64,imm64 uop。

在缓存中遇到的负载具有非常好的吞吐量(每个时钟有2个负载,而在这种情况下,微融合意味着没有额外的uop来使用内存操作数而不是{ {1}} )。因此,从内存中使用常量的主要原因是额外的缓存占用空间和缓存未命中,但是您的微基准测试根本不会显示出来。它对装载端口没有其他压力。

在一般情况下:

如果可能,请使用RIP相对cmp生成64位地址常量 例如lea是的,这需要额外的指令才能将常量输入寄存器。 (顺便说一句,只需使用lea rax, [rel addr64]。如果需要,可以使用default rel

如果使用默认(小)代码模型构建位置相关代码,则可以避免额外的指令因此静态地址适合虚拟地址空间的低32位并且可以用作immediates 。 (实际上低2GiB,因此符号或零扩展两种工作)。如果gcc抱怨绝对寻址,请参见32-bit absolute addresses no longer allowed in x86-64 Linux?;大多数发行版默认启用[abs fs:0]。这当然不适用于Linux共享库,它只支持64位地址的文本重定位。但是,您应该尽可能使用-pie来制作位置无关的代码来避免重定位。

大多数整数构建时常数适合32位,因此即使在PIC代码中也可以使用leacmp r64, imm32

如果确实需要64位非地址常量,请尝试将cmp r32, imm32提升出一个循环。如果mov r64, imm64不在循环内,那么cmp循环就可以了。 x86-64有足够的寄存器,您(或编译器)通常可以避免在整数代码中的最内层循环内重新加载。