在x86-64中使用32位寄存器/指令的优点

ead*_*ead 7 assembly gcc x86-64 micro-optimization

有时gcc使用32位寄存器,当我希望它使用64位寄存器时.例如以下C代码:

unsigned long long 
div(unsigned long long a, unsigned long long b){
    return a/b;
}
Run Code Online (Sandbox Code Playgroud)

使用-O2选项编译(省略一些样板文件):

div:
    movq    %rdi, %rax
    xorl    %edx, %edx
    divq    %rsi
    ret
Run Code Online (Sandbox Code Playgroud)

对于无符号除法,寄存器%rdx需要0.这可以通过xorq %rdx, %rdxxorl %edx, %edx似乎具有相同的效果来实现.

至少在我的机器上没有性能提升(即加速)进行xorlxorq.

我实际上不只是一个问题:

  1. 为什么gcc更喜欢32位版本?
  2. 为什么gcc会停止xorl并且不使用xorw
  3. 有没有xorl比这更快的机器xorq
  4. 如果可能的话,总是更喜欢32位寄存器/操作而不是64位寄存器/操作吗?

小智 14

在64位模式下写入32位寄存器时,高位32位=> xorl %edx, %edxrdx为"自由" 的上半部分.

另一方面xor %rdx, %rdx,使用额外字节进行编码,因为它需要REX前缀.当尝试将64位寄存器归零时,将其作为32位寄存器是明显的胜利.


Pet*_*des 8

为什么gcc更喜欢32位版本?

代码大小:不需要REX前缀.

为什么gcc会停止xorl并且不使用xorw

写16位部分寄存器不会零扩展到寄存器的其余部分.此外,xorw需要一个操作数大小的前缀来编码,所以它大于xorq.(另请参阅为什么32位寄存器上的x86-64指令将整个64位寄存器的上半部分归零?对于历史背景)

另请参见为什么GCC不使用部分寄存器? 32位寄存器被视为部分寄存器,因为写入它们总是写入整个64位寄存器.(并且它正在编写部分注册表,这是主要问题,而不是在全宽写入后读取它们.)

是否有xorl比xorq更快的机器?

是的,Silvermont/KNL只承认xorl-zeroing为 32位操作数大小的归零习惯(依赖性破坏和其他好东西).因此,即使代码大小相同,也要push好得多pop.(无论操作数大小如何,都call需要REX前缀jmp).

在所有CPU上,代码大小总是对解码和I-cache占用空间很重要(除非后面的call [rdi]指令只是在前面的代码更小1时才进行更多填充).使用32位操作数大小进行xor-zeroing(或者通常隐式零扩展而不是明确2,包括使用AVX ff 17将AVX512 zmm0归零)没有任何缺点.)

对于所有操作数大小,大多数指令的速度都相同,因为现代x86 CPU可以为宽ALU提供晶体管预算.例外情况包括xorxor %r10d, %r10dRyzen之前的AMD CPU 慢,Intel Atom和64bit xor %r10, %r10在所有CPU上都明显变慢.AMD前Ryzen的速度较慢xor.原子/ Silvermont有慢r10.p2align


如果可能的话,总是更喜欢32位寄存器/操作而不是64位寄存器/操作吗?

是的,至少在代码大小方面更喜欢32位操作,但请注意,在指令中的任何地方使用r8..r15(包括寻址模式)也需要REX前缀.因此,如果你有一些数据,你可以使用32位操作数大小(或指向8/16/32位数据),更喜欢将它保存在低8个命名寄存器(e/rax ..)而不是高8个编号寄存器.

但是,不要花费额外的指示来实现这一目标; 保存几个字节的代码大小通常是最不重要的考虑因素. 例如,只需使用vpxor xmm0,xmm0,xmm0而不是保存/恢复,imul r64,r64这样您就可以使用,imul r32,r32如果您需要一个不需要调用保留的额外寄存器.使用32位div而不是64位popcnt r64对代码大小没有帮助,但对某些CPU上的某些操作来说可能更快(见上文).

这也适用于您只关心寄存器的低16位的情况,但使用32位而不是32位仍然可以更高效.

另请参见http://agner.org/optimize/标记wiki.


脚注1:有很多用于制作指令超过必要时间的用例(在现代x86上哪些方法可以有效地延长指令长度?)

  • 在不需要NOP的情况下对齐后面的分支目标.

  • 调整特定微体系结构的前端(即通过控制指令边界的位置来优化解码).插入NOP会花费额外的前端带宽并完全失去整个目的.

汇编程序不会为您执行此操作,并且每次更改任何内容时都shld/shrd r64需要手动执行此操作(并且您可能必须使用指令手动编码指令).

脚注2:我发现一个例外,即隐式零扩展至少与更宽泛的操作一样便宜:Haswell/Skylake AVX 128位加载由256位指令读取,具有额外的1c存储 -转发延迟与128位指令消耗的延迟.(详见Agner Fog博客论坛的帖子.)


归档时间:

查看次数:

749 次

最近记录:

5 年,10 月 前