The x86-64 ABI 要求调用可变参数函数(如printf)设置%al = xmm 寄存器中传递的浮点参数的计数。在这种情况下,您传递了一个double,因此ABI 需要%al = 1。 (有趣的事实:C 的提升规则使得无法将 float 传递给 vararg 函数。这就是为什么 float 没有 printf 转换说明符,只有 double。)
mov $1, %eax 避免了对 eax 其余部分的错误依赖,(与 mov $1, %al 相比),因此 gcc 更愿意为此花费额外的指令字节,即使它正在调整 Core2(重命名部分寄存器)。
上一个答案,在澄清问题是为什么 mov 已完成,而不是其顺序之前。
IIRC,gcc 并没有为 x86 做太多的指令调度,因为它假设了乱序执行。我试图用谷歌搜索,但没有找到我似乎记得读过的 gcc 开发人员的报价(可能在 gcc 错误报告评论中)。
不管怎样,我觉得它没问题,除非你正在为有序 Atom 或 P5 进行调整。如果是,请使用gcc -O3 -march=atom(这意味着-mtune=atom)。但无论如何,您显然没有这样做,因为您在 C2Duo 上使用了 -march=native,这是一个 4 宽的乱序设计,具有相当大的调度程序。
对我来说,由于 cpu 重新排序和不同的执行上下文,这条交错指令是无用的。
我不知道您认为问题出在哪里,或者您认为哪种排序会更好,所以我将解释为什么它看起来不错。
我没有花时间将其编辑为简短的答案,因此您可能更愿意阅读 Agner Fog's microarch pdf 了解 Core2 管道的详细信息,然后略读此答案。另请参阅 x86 标签 wiki 中的其他链接。
...
call _atof
# xmm0 is probably still not ready when the following instructions issue
pxor %xmm1, %xmm1 # no inputs, so can run any time after being issued.
gcc 使用pxor,因为cvtsi2sd is badly designed,给它一个错误的依赖向量寄存器的前一个值。请注意向量寄存器的上半部分如何保持其旧值。英特尔可能是这样设计的,因为最初的 SSE cvtsi2ss 最初是在 Pentium III 上实现的,其中 128b 向量被分为两半处理。将寄存器的其余部分(包括上半部分)归零而不是合并可能会对 PIII 产生额外的影响。
这种短视的设计选择让架构不得不在额外的依赖破坏指令或错误依赖之间做出选择。错误的 dep 可能根本不重要,或者如果一个函数使用的寄存器碰巧用于另一个函数中非常长的 FP 依赖链(可能包括缓存未命中),则可能会大大减慢速度。
在 Intel SnB 系列 CPU 上,xor-zeroing is handled at register-rename time,因此 uop 永远不需要在执行端口上执行;它在发布到 ROB 后就已经完成。这适用于整数和向量寄存器。
在其他 CPU 上,pxor 将需要一个执行端口,但没有输入依赖项,因此它可以在有空闲 ALU 端口后随时执行。
movl $1, %eax # no input dependencies, can execute any time.
这条指令可以放在call atof之后和call printf之前的任何地方。
cvtsi2sd %ebx, %xmm1 # no false dependency thanks to pxor.
根据 Agner Fog 的表格,这是 Core2(Merom 和 Penryn)上的 2 uop 指令。这很奇怪,因为cvtsi2ss 是 1 uop。 (它们都是 SnB 中的 2 个 uop;大概是一个 uop 在整数和向量之间移动数据,另一个用于转换)。
早点放这个insn会很好,可能会早一点发出它,因为它是这里最长的依赖链的一部分。 (整数的东西都是简单而微不足道的)。但是,printf 在决定查看xmm0 之前必须解析格式字符串,因此 FP 指令实际上并不在关键路径上。
它不能领先于pxor,而call / pxor / cvtsi2sd 将意味着pxor 将在该循环中自行解码。解码将从call 之后的指令开始,在被调用函数中的ret 被解码之后(并且返回地址预测器预测调用后跳转回insn)。多 uop 指令必须是块中的第一条指令,因此让 pxor 和 mov imm32 解码该循环意味着更少的解码瓶颈。
leaq LC0(%rip), %rdi # 1 uop
addsd %xmm1, %xmm0 # 1 uop
call _printf # 3 uop insn
cvtsi2sd/lea/addsd可以在同一个周期内解码,这是最优的。如果mov imm32 在 cvt 之后,它也可以在同一个周期内解码(因为 pre-SnB 解码器最多可以处理 4-1-1-1),但它不可能尽快发出。
如果解码只是勉强跟上问题,那意味着pxor 将自行发出(因为尚未解码其他指令)。然后是cvtsi2sd/mov imm/lea(4 微指令),然后是addsd/call(4 微指令)。 (addsd 与上一个问题组解码;core2 在解码和问题之间有一个短队列,以帮助吸收这样的解码气泡,并使其能够在一个周期内解码多达 7 微指令。)
这与解码瓶颈情况下的当前问题模式没有明显不同:(pxor / mov imm) / (cvtsi2sd/lea/addsd) / (call printf)
如果解码不是瓶颈,我不确定 Core2 是否可以在与跳转后的微指令相同的周期内发出 ret 或 jmp。在 SnB 系列 CPU 中,无条件跳转总是结束一个问题组。例如3-uop 循环问题 ABC、ABC、ABC,而不是 ABCA、BCAB、CABC。
假设在ret 问题后的说明与不包括ret 的组一起出现,我们会有
(pxor/mov imm/cvtsi2sd), (lea / addsd / call 的 3 个微指令中的 2 个) / (最后一个 call 微指令)
所以 cvtsi2sd 在从atof 返回后的第一个周期仍然发出问题,这意味着它可以立即开始执行。即使在pxor 采用执行单元的Core2 上,来自cvtsi2sd 的2 个微指令中的第一个也可能在与pxor 相同的周期内执行。可能只有第二个微指令依赖于 dst 寄存器。
(mov imm / pxor / cvtsi2sd) 将是等效的,解码速度较慢的 (pxor / cvtsi2sd / mov imm) 或执行 lea 也是等效的在mov imm之前。