为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

GCC为什么不使用分寄存器?拆解write(1,"hi",3)在Linux上构建的gcc -s -nostdlib -nostartfiles -O3成果如下:ba03000000     mov edx, 3 ; thanks for the correction jester!bf01000000     mov edi, 131c0           xor eax, eax e9d8ffffff     jmp loc.imp.write我不喜欢编译器开发,但是由于这些寄存器中的每个值都是常量且已知的编译时,我很好奇为什么GCC不使用dl, dil,和al相反。有些人可能会说,这个特性在性能上不会有任何不同,但是在可执行文件的大小上有很大的差别。mov $1, %rax => b801000000和mov $1, %al => b001当我们谈论一个程序中成千上万的寄存器访问时。这不仅是软件优雅的一部分,而且对性能也有影响。有人能解释一下为什么“GCC”决定不重要吗?
查看完整描述

3 回答

?
湖上湖

TA贡献2003条经验 获得超2个赞

在许多x86处理器上,部分寄存器会导致性能损失,因为在编写时,它们被重命名为与整个寄存器不同的物理寄存器。(有关启用无序执行的寄存器重命名的更多信息,请参见这个问答).

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

这叫做部分寄存器失速阿格纳·福格显微建筑手册很好地解释了:

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中的行为:

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

这就是为什么编译器大多避免写字部分寄存器他们用movzx / movsx只要有可能,将窄值扩展到完全寄存器,以避免部分寄存器错误依赖(AMD)或暂存(Intel P6-系列)。因此,大多数现代机器代码并不能从部分寄存器重命名中获益,这就是最近Intel CPU正在简化它们的部分寄存器重命名逻辑的原因。

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


也请注意那,那个write获取参数,对于通常配置为GCC的x86-64,需要完整的32位和64位寄存器,这样就不能简单地组装到mov dl, 3..大小由类型的数据,而不是价值数据。

最后,在某些情况下,C默认参数提升要知道,虽然事实并非如此.
实际上,作为罗斯里奇指出,这个电话很可能是在没有可见原型的情况下发出的。


正如杰斯特指出的那样,你的拆卸是误导人的。
例如mov rdx, 3实际上mov edx, 3,尽管两者都有相同的效果-也就是说,将3放在整体中。rdx.
这是正确的,因为即时值3不需要符号扩展名和MOV r32, imm32隐式清除寄存器的上32位。


查看完整回答
反对 回复 2019-07-03
?
德玛西亚99

TA贡献1770条经验 获得超3个赞

事实上,GCC经常使用分式寄存器。..如果您查看生成的代码,您将发现很多使用部分寄存器的情况。

简短的回答你的特殊情况,是因为GCC在调用cabi函数时总是将参数符号或零扩展到32位。.

这个事实上SysV x 86和x86-64 abigccclang要求小于32位的参数为零或符号扩展为32位.有趣的是,它们不需要一直延伸到64位。

因此,对于64位平台SysVABI平台上的如下功能:

void foo(short s) {
 ...}

..争论s传入rdi而s的部分如下(但请参阅下面关于icc):

  bits 0-31:  SSSSSSSS SSSSSSSS SPPPPPPP PPPPPPPP
  bits 32-63: XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX  where:
  P: the bottom 15 bits of the value of `s`
  S: the sign bit of `s` (extended into bits 16-31)
  X: arbitrary garbage

密码foo可以依赖于SP比特,但不是在X比特,这可能是任何东西。

同样,对于foo_unsigned(unsigned short u),你会0第16-31段,否则就会是相同的。

注意我说过事实上-因为实际上并没有真正记录如何处理较小的返回类型,但是您可以看到彼得的回答详情请到这里。我还问了一个相关的问题这里.

经过进一步的测试,我得出结论:icc实际上违反了这个事实上的标准。gccclang似乎很坚持,但是gcc只以保守的方式:什么时候呼叫函数,它将零/符号扩展参数扩展到32位,但在其函数实现中,依附在打电话的人身上。clang实现依赖于调用者将参数扩展到32位的函数。所以事实上clangicc即使对于普通C函数,如果它们的参数小于int.


查看完整回答
反对 回复 2019-07-03
?
凤凰求蛊

TA贡献1825条经验 获得超4个赞

在类似于最初的IBMPC上,如果已知AH包含0,并且有必要以0x34这样的值加载AX,则使用“MOVAL,34H”通常需要8个周期,而不是“MOVAX,0034h”所需的12个周期-这是一个相当大的速度改进(如果预取的话,这两条指令都可以在两个周期内执行,但在实践中,8088花费了大部分时间等待指令被以每字节四个周期的代价获取)。然而,在当今通用计算机中使用的处理器上,获取代码所需的时间通常并不是影响总体执行速度的一个重要因素,代码大小通常也不是一个特别关注的问题。

此外,处理器厂商试图最大限度地提高人们可能运行的代码类型的性能,而8位加载指令不太可能像现在32位加载指令那样经常使用。处理器核心通常包括同时执行多个32位或64位指令的逻辑,但可能不包括与任何其他操作同时执行8位操作的逻辑。因此,尽管在8088上使用8位操作(如果可能的话)是对8088的一个有用的优化,但它实际上对新的处理器来说是一个严重的性能损耗。


查看完整回答
反对 回复 2019-07-03
  • 3 回答
  • 0 关注
  • 539 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信