当前位置:  开发笔记 > 运维 > 正文

为什么GCC不使用部分寄存器?

如何解决《为什么GCC不使用部分寄存器?》经验,为你挑选了1个好方法。

write(1,"hi",3)在linux上反汇编,gcc -s -nostdlib -nostartfiles -O3结果如下:

ba03000000     mov edx, 3 ; thanks for the correction jester!
bf01000000     mov edi, 1
31c0           xor eax, eax
e9d8ffffff     jmp loc.imp.write

我不是到编译器的开发,但由于移动到这些寄存器的每一个值是恒定的和已知的编译时间,我很好奇,为什么不GCC使用dl,dilal来代替.也许有人会说,此功能不会让任何性能上的差异,但有一个在之间的可执行文件的大小有很大的区别mov $1, %rax => b801000000,并mov $1, %al => b001当我们谈论数千寄存器的程序访问.如果软件的优雅部分不仅体积小,它确实会对性能产生影响.

有人可以解释为什么"海湾合作委员会决定"它无所谓?



1> Margaret Blo..:

部分寄存器会对许多x86处理器造成性能损失,因为它们在写入时会重新命名为不同的物理寄存器.(有关启用无序执行的寄存器重命名的更多信息,请参阅此问答).

但是当指令读取整个寄存器时,CPU必须检测到它在单个物理寄存器中没有正确的架构寄存器值这一事实.(这发生在问题/重命名阶段,因为CPU准备将uop发送到无序调度程序.)

它被称为部分寄存器停顿.Agner Fog的微体系结构手册很好地解释了它:

6.8部分登记档位 (PPro/PII/PIII和早期Pentium-M)

部分寄存器停顿是一个问题,当我们写入32位寄存器的一部分,然后从整个寄存器或更大的部分读取时,会发生这种情况.
例:

; Example 6.10a. Partial register stall
mov al, byte ptr [mem8]
mov ebx, eax ; Partial register stall

这给出了5到6个时钟的延迟.原因是已经分配了一个临时寄存器AL以使其独立AH.执行单元必须等到写入AL已经退出,然后才能将值AL与其余的值 组合起来EAX.

不同CPU中的行为:

英特尔早期P6系列:见上文:停止5-6个时钟,直到部分写入退出.

Intel Pentium-M(型号D)/ Core2/Nehalem:插入合并uop时停止2-3个周期.(有关使用或不使用xor-zeroing读取EAX的微基准测试,请参阅此Q&A)

Intel Sandybridge:插入一个合并的uop用于low8/low16(AL/AX)而不会停止,或者用于AH/BH/CH/DH,同时停止1个周期.

英特尔IvyBridge(可能),但绝对是Haswell/Skylake:AL/AX没有重命名,但AH仍然是:Haswell/Skylake的部分寄存器究竟是 如何执行的?写AL似乎对RAX有假依赖,而AH是不一致的.

所有其他x86 CPU:Intel Pentium4,Atom/Silvermont/Knight's Landing.所有AMD(以及Via等):

部分寄存器永远不会重命名.写入部分寄存器合并到完整寄存器中,使写入取决于完整寄存器的旧值作为输入.

如果您从未读取完整寄存器,则在没有部分寄存器重命名的情况下,写入的输入依赖性是错误依赖性.这限制了指令级并行性,因为将8位或16位寄存器重新用于其他内容实际上并不独立于CPU的观点(16位代码可以访问32位寄存器,因此它必须在上层保持正确的值半).而且,它使AL和AH不独立.当英特尔设计P6系列(1993年发布的PPro)时,16位代码仍然很常见,因此部分寄存器重命名是使现有机器代码运行更快的重要特性.(实际上,许多二进制文件不会为新CPU重新编译.)

这就是编译器大多避免编写部分寄存器的原因.它们使用movzx/ movsx尽可能将窄值零或符号扩展为完整寄存器,以避免部分寄存器错误依赖(AMD)或停顿(Intel P6系列).因此,大多数现代机器代码并没有从部分寄存器重命名中获益,这就是为什么最近的英特尔CPU正在简化其部分寄存器重命名逻辑.

正如@ BeeOnRope的回答指出的那样,编译器仍然会读取部分寄存器,因为这不是问题.(阅读AH/BH/CH/DH可以在Haswell/Skylake上增加额外的延迟周期,但是,请参阅早期关于Sandybridge家族最近成员的部分注册的链接.)


还要注意的是write接受参数,对于一个x86-64的通常被配置GCC,需要整个的32位和64位寄存器,因此不能简单地组装成mov dl, 3.大小由数据类型决定,而不是数据.

最后,在某些情况下,C有默认参数提升要注意,尽管情况并非如此.
实际上,正如RossRidge指出的那样,调用可能是在没有可见原型的情况下进行的.


正如@Jester指出的那样,你的反汇编会产生误导.
例如mov rdx, 3,实际上mov edx, 3,虽然两者具有相同的效果 - 即整体上放3个rdx.
这是正确的,因为立即值3不需要符号扩展,并且MOV r32, imm32隐式清除寄存器的高32位.

推荐阅读
地之南_816
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有