Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/visual-studio-2012/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Assembly 为什么GCC选择dword movl将长移位计数复制到CL?_Assembly_Gcc_X86 64_Micro Optimization - Fatal编程技术网

Assembly 为什么GCC选择dword movl将长移位计数复制到CL?

Assembly 为什么GCC选择dword movl将长移位计数复制到CL?,assembly,gcc,x86-64,micro-optimization,Assembly,Gcc,X86 64,Micro Optimization,在《计算机系统:程序员展望》的第三章中,在讨论移位操作时给出了一个示例程序: long shift_left4_rightn(长x,长n) { x=n; 返回x; } 其组装代码如下所示(可与.-O2以不同顺序调度指令,但仍使用movl到ECX): 左移4右移:   endbr64   movq   %rdi,%rax    获取x   salq   $4%rax      x=n   ret 我想知道为什么获取n的汇编代码是movl %esi,%ecx而不是movq %rsi,%rcx因为n

在《计算机系统:程序员展望》的第三章中,在讨论移位操作时给出了一个示例程序:

long shift_left4_rightn(长x,长n)
{
x=n;
返回x;
}
其组装代码如下所示(可与.
-O2
以不同顺序调度指令,但仍使用
movl
到ECX):

左移4右移:
  endbr64
  movq   %rdi,%rax    获取x
  salq   $4%rax      x=n
  ret

我想知道为什么获取n的汇编代码是
movl %esi,%ecx
而不是
movq %rsi,%rcx
因为
n
是一个四字

另一方面,
movb %如果考虑优化,则sil,%cl
可能更合适,因为移位量仅使用单字节寄存器元素
%cl
,并且这些较高的位都被忽略


因此,我真的无法找出使用“movl”的原因 %esi,%ecx“在处理长整数时。

如果可能,编译器更喜欢32位寄存器而不是64位寄存器,因为使用64位寄存器需要额外的“REX”前缀字节


这同样适用于选择
rsi\esi
寄存器的最低字节,这在32位编码中不可用,因此需要前缀。正如Peter Cordes所评论的,编译器通常避免使用8位寄存器,这是由于调用了时间惩罚,这是CPU如何检测依赖链、无序执行和重命名寄存器的内部原因。

是的,GCC意识到高位被
sar
忽略
然后
movl
是应用两个简单优化规则的自然结果:

  • 避免写入部分寄存器(即8位或16位,写入合并到旧值中,而不是零扩展)由于不同微体系结构的各种原因,包括在本例中,对旧值RCX的错误依赖
  • 因为它是x86-64机器代码中的默认值,不需要任何前缀。对于任何指令,它至少与任何其他操作数大小一样快
有趣的事实:即使参数是
uint8\t
,编译器仍希望使用
movl%esi,%ecx
。您可能认为,当arg值仅在SIL中时读取更宽的寄存器可能会造成部分寄存器暂停,但x86-64 SystemV调用约定的非官方扩展就是这样。所以我们可以假设它至少是用32位操作编写的

其他一些选择的具体缺点:

  • movq%rsi,%rcx
    -浪费REX前缀(代码大小下降)
  • movb%sil,%cl
    -写入部分寄存器,仍然需要REX前缀才能访问sil
  • movzbl%sil,%ecx
    -代码大小:2字节操作码,需要REX读取sil。此外,AMD CPU仅对
    movl
    /
    movq
    执行mov消除(零延迟),而不是movzx
  • movw%si,%cx
    -零优势,需要操作数大小前缀并写入部分寄存器
  • movzwl%si,%ecx
    -在代码大小方面与
    movq
    保持一致,但即使在Intel CPU上也无法消除mov

有趣的事实:如果我们用一个伪参数填充,因此
n
到达RDX,GCC仍然选择
movl%edx,%ecx
,即使
movb%dl,%cl
是相同的代码大小(不需要REX访问dl)。所以,是的,GCC肯定在避免字节操作数大小

有趣的事实2:Clang不幸地在
movq
上浪费了一个REX,错过了这个优化


但如果我们将计数arg
unsigned char
,幸运的是,clang和GCC都使用
movl
,而不是
movb

非常确定GCC避免了SIL,因为它尽可能避免了部分寄存器欺骗,如果arg已经到达EDX,它将做出相同的选择。这里不需要部分寄存器暂停
movb
将写入部分寄存器,但不需要读取完整寄存器。(这是在叶函数中调用clobbered reg)。因此,部分寄存器重命名实际上可以使其完全正常。问题不在于部分寄存器暂停,而在于CPU上没有将低位字节与完整寄存器分开重命名(Intel Haswell和更高版本,以及任何非Intel)。在这里,您将对完整寄存器有一个错误的依赖关系。避免这种错误的dep是Intel P6系列进行部分寄存器重命名的原因,这在这里非常有效。(但一般来说,编译器可能不会仔细分析,只要有可能,就尽量避免编写8位寄存器,即使使用
-mtune=nehalem