Performance 与mov reg、imm64相比,RIP相对寻址的性能如何?

Performance 与mov reg、imm64相比,RIP相对寻址的性能如何?,performance,assembly,x86-64,Performance,Assembly,X86 64,众所周知,x86-64指令不支持64位立即数(mov除外)。因此,当将代码从32位迁移到64位时,如下所示的指令: cmp rax, addr32 mov r11, addr64 ; scratch register cmp rax, r11 不能替换为以下内容: cmp rax, addr64 在这种情况下,我考虑两种选择:(a)使用暂存寄存器加载常量或(b)使用rip相对寻址。这两种方法如下所示: cmp rax, addr32 mov

众所周知,x86-64指令不支持64位立即数(mov除外)。因此,当将代码从32位迁移到64位时,如下所示的指令:

    cmp rax, addr32
    mov r11, addr64 ; scratch register
    cmp rax, r11
不能替换为以下内容:

    cmp rax, addr64
在这种情况下,我考虑两种选择:(a)使用暂存寄存器加载常量或(b)使用rip相对寻址。这两种方法如下所示:

    cmp rax, addr32
    mov r11, addr64 ; scratch register
    cmp rax, r11

我写了一个非常简单的循环来比较两种方法的性能(我在下面粘贴)。虽然(b)使用间接指针,(a)在指令中编码了立即数(这可能会导致i-cache的更糟糕使用)。令人惊讶的是,我发现(b)跑得比(a)快约10%。在更常见的实际代码中,这是预期的结果吗


令人惊讶的是,我发现(b)跑得比(a)快约10%

您可能在AMD推土机系列或Ryzen以外的CPU上进行了测试,后者具有快速
循环
指令。在其他CPU上,,因此您在它上遇到了瓶颈。e、 g.7个UOP,Haswell上每5c吞吐量一个

mov r64、imm64
对uop缓存吞吐量不利,因为在Intel的uop缓存中,大的立即数占用了2个插槽。(请参阅中的Sandybridge uop缓存部分),我在其中列出了详细信息

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

在Intel上,
loop
是7个uops,在大多数CPU上是每5个时钟一个吞吐量,因此每4个时钟的问题/重命名瓶颈不会是您遇到的问题<代码>循环
是微编码的,因此前端不能从循环缓冲区运行。(Skylake CPU的LSD被微码更新禁用,以修复部分寄存器勘误表。)因此每次通过循环时都必须从uop缓存中重新读取
mov r64,imm64
uop


命中缓存的加载具有很好的吞吐量(每个时钟2次加载,在这种情况下,微融合意味着不需要额外的UOP来使用内存操作数而不是cmp的寄存器)。因此,从内存中使用常量的主要缺点是额外的缓存占用空间和缓存未命中,但您的微基准不会显示这一点。它在负载端口上也没有其他压力


在一般情况下: 如果可能,使用RIP相对
lea
生成64位地址常量。
e、 g.
lea-rax,[rel addr64]
是的,这需要额外的指令才能将常数放入寄存器。(顺便说一句,只需使用
默认rel
。如果需要,您可以使用
[abs fs:0]

如果使用默认(小)代码模型构建位置相关的代码,则可以避免额外的指令,使静态地址适合低32位的虚拟地址空间,并可用作即时地址(实际上是低2GiB,因此符号或零扩展这两个工作)。查看gcc是否抱怨绝对寻址;
-pie
在大多数发行版上默认为启用。这在Linux共享库中当然不起作用,因为Linux共享库只支持64位地址的文本重定位。但您应该尽可能避免重定位,方法是使用
lea
生成位置无关的代码

大多数整数构建时间常数适合32位,因此您甚至可以在PIC代码中使用
CMPR64、imm32
CMPR32、imm32


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

mov rcx,0x1
/
shl rcx,30
mov ecx相比没有任何优势,1是的,我知道,我只是很懒,不想写一个非常大的常数,并且计数为零:)。至于
rax
,我对它的价值不感兴趣,但对
je next
不感兴趣。有一个不可预测的初始值在我看来似乎是一个很好的属性,但我想这并不重要,只是让人困惑。这就是为什么你让汇编程序为你设定常数,但实际上写的是
mov ecx,1相关:/