C 朴素的阶梯逻辑比分解逻辑好吗?
我是新来的 我正在测试一个具有嵌套逻辑的例程的两个实现:一个是幼稚的实现,另一个是我聪明地尝试删除一些分支的实现。我在x86Merom上使用了“gcc(Ubuntu/Linaro4.6.3-1ubuntu5)4.6.3”和gcc选项“-ffast math-fomit frame pointer-msseregparm-mfpmath=sse-msse2”。代码如下: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
#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
本身不应该做任何影响正确性的事情(对于一开始就正确的代码或其他东西)。