天真的阶梯逻辑比分解逻辑更好吗?

时间:2014-04-22 05:09:52

标签: c sse

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的链接,慷慨地接受。

1 个答案:

答案 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。基于掩码寄存器,cmovandps / pand填充blendv