sse newb here ...
我正在测试具有嵌套逻辑的例程的两个实现:一个天真的实现和一个我很聪明地试图删除一些分支的实现。我在x86 Merom上使用'gcc(Ubuntu / Linaro 4.6.3-1ubuntu5)4.6.3'和gcc选项'-ffast-math -fomit-frame-pointer -msseregparm -mfpmath = sse -msse2'。代码如下:
#define math_sign(a) ( (a) < .0f ? -1.f : +1.f )
inline float math_interp_clamp(float a, float slope, float target)
{
#if 0
// 5 instr, 1 branch
float b = a + slope;
return slope > 0.f ? (b > target ? target : b) : (b < target ? target : b);
#else
// 19 instr
float b = a + slope;
return ( b - target ) * math_sign( slope ) > 0.f ? target : b;
#endif
}
启用ifdef后,我得到:
math_interp_clamp:
.LFB505:
.cfi_startproc
comiss .LC7, %xmm1
addss %xmm1, %xmm0
jbe .L44
minss %xmm0, %xmm2
movaps %xmm2, %xmm0
ret
.L44:
maxss %xmm0, %xmm2
movaps %xmm2, %xmm0
ret
.cfi_endproc
我的ifdef被禁用了,我得到了:
math_interp_clamp:
.LFB505:
.cfi_startproc
xorps %xmm5, %xmm5
addss %xmm1, %xmm0
movss .LC3, %xmm4
cmpltss %xmm5, %xmm1
movss .LC2, %xmm6
movaps %xmm0, %xmm3
andps %xmm1, %xmm4
andnps %xmm6, %xmm1
subss %xmm2, %xmm3
orps %xmm4, %xmm1
mulss %xmm1, %xmm3
movaps %xmm5, %xmm1
cmpltss %xmm3, %xmm1
movaps %xmm2, %xmm3
movaps %xmm1, %xmm2
andps %xmm1, %xmm3
andnps %xmm0, %xmm2
orps %xmm3, %xmm2
movaps %xmm2, %xmm0
ret
.cfi_endproc
我实际上没有对生成的代码进行计时,但是基于循环计数,我无法想象那些19条指令比单纯的分支更快...我应该如何无情地避免分支,或者我是否使用gcc错了吗?
与优秀的时间表或sse-tutorial的链接,慷慨地接受。
答案 0 :(得分:1)
gcc 4.9.2将无分支版本编译为16条指令。或者19与-m32 -msse
但不是-m32 -msse2
相似。这些指令中存在相当大的并行度,但遗憾的是FP逻辑(如orps
)只能在当前英特尔设计中的单个端口上运行。 (与能够接受por
uops的所有3个矢量ALU端口相比。)
gcc用-O3 -ffast-math
无分支地编译你的“天真”版本。 (没有-ffast-math
,它仍然使用分支。我没有检查位操作以查看结果是否与NaN相同。)可能有一种方法来编写您的天真版本,以便它可以创建一个无分支版本,即使没有-ffast-math
。可能是!(b < target)
代替b > target
,所以NaN处理是一样的吗? IDK。
math_interp_clamp: # gcc 4.9.2 -O3 -ffast-math
.LFB0:
addss %xmm1, %xmm0
movaps %xmm0, %xmm3
maxss %xmm2, %xmm3
minss %xmm0, %xmm2
pxor %xmm0, %xmm0
cmpltss %xmm1, %xmm0
andps %xmm0, %xmm2
andnps %xmm3, %xmm0
orps %xmm2, %xmm0
ret
这可能是一场胜利,除非分支是非常可预测的(即价值几乎从不需要钳制)。你最好的是-fprofile-generate
/ -fprofile-use
,让编译器决定。
出于好奇,我用x87(-m32 -O3
)查看了无分支版本。即使x87具有fcmovbe
,它仍然可以编译为19个insn,因为x87需要额外的指令才能弹出堆栈(并且函数args不会在regs中启动)。
基于xmm寄存器的标志,没有cmov
。基于掩码寄存器,cmov
或andps / pand
填充blendv
。