C 朴素的阶梯逻辑比分解逻辑好吗?

C 朴素的阶梯逻辑比分解逻辑好吗?,c,sse,C,Sse,我是新来的 我正在测试一个具有嵌套逻辑的例程的两个实现:一个是幼稚的实现,另一个是我聪明地尝试删除一些分支的实现。我在x86Merom上使用了“gcc(Ubuntu/Linaro4.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 floa

我是新来的

我正在测试一个具有嵌套逻辑的例程的两个实现:一个是幼稚的实现,另一个是我聪明地尝试删除一些分支的实现。我在x86Merom上使用了“gcc(Ubuntu/Linaro4.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
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是错误的


链接到一个好时机的howto或sse教程,我们欣然接受。

gcc 4.9.2将无分支版本编译为16条指令。或者像您一样使用
-m32-msse
但不使用
-m32-msse2
。这些指令有相当多的并行性,但不幸的是,在当前的英特尔设计中,FP逻辑(如
orps
)只能在单个端口上运行。(与能够接受
por
uop的所有3个向量ALU端口相比。)

gcc使用
-O3-ffast math
无分支地编译您的“naive”版本。(如果没有
-ffast math
,它仍然使用分支。我还没有检查位运算以查看结果是否与NaNs相同。)可能有一种方法可以编写您的原始版本,以便它可以生成一个无分支版本,即使没有
-ffast math
。也许是通过
!(b
而不是
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生成
/
-fprofile使用
,并由编译器决定

出于好奇,我研究了x87的无分支版本(
-m32-O3
)。即使x87有
fcmovbe
,它仍然编译为19 INSN,因为x87需要额外的指令来弹出堆栈(并且函数args不在regs中启动)


没有基于xmm寄存器标志的
cmov
。打包
cmov
是基于掩码寄存器使用
和ps/pand
blendv
完成的。

这当然不是。好吧,好吧-这不是用于PLC的。虽然有两层逻辑,但我会使用一些“真实”的输入数据来对此进行基准测试。在当今高度流水线化的处理器中,分支预测失误是非常昂贵的。无分支代码的性能可能仍然优于有分支的代码。你看比如说啊。对我现在读到,我的管道是14个阶段-这可能会打乱一些事情,我想。目前,我的“内循环”只有大约100条指令长。如果分支预测器有很好的猜测概率,那么分支并不昂贵。这不可能从代码片段中猜到,它取决于斜率的实际值。需要进行分析以确定您是否领先。我们不能为您做这些。非常感谢您的分享。也许在正确的地方使用-O3会有所帮助。我没有全局使用它-它似乎与libsdl的多线程使用相冲突,所以我选择在特定部分使用O3等效的pragma。仅供参考,代码结束了。@Orthoteroid:
-O3
与多线程冲突?也许你需要在你的代码中设置内存障碍,这样它就不会被未来更智能的编译器破坏。我认为,
-O3
本身不应该做任何影响正确性的事情(对于一开始就正确的代码或其他东西)。