X86-指令交错,避免CPU停顿 [英] x86 - instruction interleaving to avoid cpu stall
问题描述
Gcc6-英特尔酷睿2二人组。 编译标志:"-3月=Native-O3"(-S)
我正在编译一个简单的程序,并要求提供汇编输出:
代码
movq 8(%rsi), %rdi
call _atoi
movq 16(%rbp), %rdi
movl %eax, %ebx
call _atof
pxor %xmm1, %xmm1
movl $1, %eax <- this instruction is my problem
cvtsi2sd %ebx, %xmm1
leaq LC0(%rip), %rdi
addsd %xmm1, %xmm0
call _printf
addq $8, %rsp
执行
读取/转换整数变量,然后读取/转换双精度值并将其相加。
问题
我完全理解一个人(编译器更是如此)必须尽可能避免CPU停止。
我已经在上面的代码部分中显示了违规指令。 对我来说,随着CPU的重新排序,以及不同的执行上下文,这种交错指令毫无用处。
我的理由是:无论如何我们停顿的可能性都很高,CPU将等待pxor
xmm1返回,然后才能在下一条指令中重用它。添加一条指令只会徒劳地填满CPU解码器。无论如何,CPU都必须等待。那么,为什么不把它放在一边,等待一条指令呢?
将pxor
移到atof之前似乎是不可能的,因为atof可以使用它。
问题
是错误、遗留垃圾(当CPU无法重新排序时)还是..否则?谢谢
编辑:
我承认我的问题不清楚:是否可以安全地删除此指令而不会影响性能?
xmm
The x86-64 ABI要求调用推荐答案函数(如printf
)set%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标签维基中的其他链接。
...
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,这给了它对向量寄存器上一个值的错误依赖。注意向量寄存器的上半部分如何保持其旧值。英特尔之所以这样设计,可能是因为最初的SSEcvtsi2ss
首先在奔腾III上实现,在奔腾III中,128B向量被作为两部分处理。将寄存器的其余部分(包括上半部分)置零而不是合并,可能会在PIII上花费额外的uop。
在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)上的2uop指令。这很奇怪,因为cvtsi2ss
是1uop。(在SNB中,它们都是两个uop;假设一个uop用于在整数和向量之间移动数据,另一个用于转换)。
printf
必须先分析格式字符串,然后才能决定查看xmm0
,因此FP指令实际上不在关键路径上。
它不能在pxor
之前,call
/pxor
/cvtsi2sd
将意味着pxor
将自己解码该循环。解码将从call
之后的指令开始,在被调用函数中的ret
被解码之后(并且返回地址预测器预测在调用之后跳回到INSN)。多微指令必须是块中的第一条指令,因此让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之后,它也可以在相同的周期内进行解码(因为SNB之前的解码器最多可以处理4-1-1-1),但它不可能很快发出。
如果解码仅勉强跟上问题,则意味着pxor
将自行发布(因为还没有对其他指令进行解码)。然后cvtsi2sd
/mov imm
/lea
(4个uop),然后addsd
/call
(4个uop)。(addsd
与前一个问题组一起解码;Core2在解码和发布之间有一个较短的队列,以帮助吸收这样的解码气泡,并使其能够在一个周期内解码多达7个uop。)
pxor
/mov imm
)/(cvtsi2sd
/lea
/addsd
)/(call printf
)
如果解码不是瓶颈,我不确定Core2是否可以在与跳转后的uop相同的周期内发出ret
或jmp
。在SNB系列CPU中,无条件跳转总是会结束一个问题组。例如,3-uop循环问题ABC
、ABC
、ABC
、NOTABCA
、BCAB
、CABC
。
假设ret
问题之后的说明不包括ret
,我们将有
(pxor
/mov imm
/cvtsi2sd
),(lea
/addsd
/2个call
uop)/(最后一个call
uop)
所以cvtsi2sd在从atof
返回后的第一个周期中仍然发出,这意味着它可以立即开始执行。即使在Core2上,pxor
采用一个执行单元,cvtsi2sd
的两个uop中的第一个也可能与pxor
在相同的周期内执行。可能只有第二个微操作对DST寄存器具有输入依赖关系。
(mov imm
/pxor
/cvtsi2sd
)是等价的,解码速度较慢的(pxor
/cvtsi2sd
/mov imm
),或者在mov imm
之前执行lea
。
这篇关于X86-指令交错,避免CPU停顿的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!