我的应用程序中有一个乘加内核,我想提高它的性能。C代码循环性能
我使用了Intel Core i7-960(3.2GHz的时钟),并已手动实现使用SSE内部函数如下内核:
for(int i=0; i<iterations; i+=4) {
y1 = _mm_set_ss(output[i]);
y2 = _mm_set_ss(output[i+1]);
y3 = _mm_set_ss(output[i+2]);
y4 = _mm_set_ss(output[i+3]);
for(k=0; k<ksize; k++){
for(l=0; l<ksize; l++){
w = _mm_set_ss(weight[i+k+l]);
x1 = _mm_set_ss(input[i+k+l]);
y1 = _mm_add_ss(y1,_mm_mul_ss(w,x1));
…
x4 = _mm_set_ss(input[i+k+l+3]);
y4 = _mm_add_ss(y4,_mm_mul_ss(w,x4));
}
}
_mm_store_ss(&output[i],y1);
_mm_store_ss(&output[i+1],y2);
_mm_store_ss(&output[i+2],y3);
_mm_store_ss(&output[i+3],y4);
}
我知道我可以使用打包FP载体,以提高性能和我已经做到了成功,但我想知道为什么单个标量代码无法达到处理器的峰值性能。这个内核在我的机器上的性能是每个周期〜1.6个FP操作,而每个周期最大为2个FP操作(因为FP add + FP mul可以并行执行)。
如果我正确研究生成的汇编代码,理想的时间表如下所示,其中mov
指令需要3个周期,从相关指令开始,从加载域到FP域的切换延迟需要2个周期,FP乘法需要4个周期,FP加法需要3个周期。 (请注意,乘法 - >添加的相关性不会导致任何切换延迟,因为这些操作属于同一个域)。
根据所测量的性能(〜最大理论性能的80%)存在的每8个周期〜3个指令的开销。
我想要么:
- 摆脱这种开销,或
- 解释它来自哪里
当然有与缓存的问题错过&数据错位,其可以增加移动指令的延迟,但是还有其他因素可以在这里起作用吗?像寄存器读取摊位什么的?
我希望我的问题很清楚,在此先感谢您的回复!
更新:内环的组装如下所示:
...
Block 21:
movssl (%rsi,%rdi,4), %xmm4
movssl (%rcx,%rdi,4), %xmm0
movssl 0x4(%rcx,%rdi,4), %xmm1
movssl 0x8(%rcx,%rdi,4), %xmm2
movssl 0xc(%rcx,%rdi,4), %xmm3
inc %rdi
mulss %xmm4, %xmm0
cmp $0x32, %rdi
mulss %xmm4, %xmm1
mulss %xmm4, %xmm2
mulss %xmm3, %xmm4
addss %xmm0, %xmm5
addss %xmm1, %xmm6
addss %xmm2, %xmm7
addss %xmm4, %xmm8
jl 0x401b52 <Block 21>
...
它确实依赖于很多编译器(甚至它的版本)以及您传递给它的优化标志。如果数值性能对您来说如此重要,您可能还需要花时间和精力学习数值库和/或OpenCL或CUDA(以利用GPGPU)。 Ther也是缓存考虑因素。在现在的处理器上预测循环的实际时间很困难。 – 2012-04-03 11:13:37
我不明白为什么你会认为循环控制总是可以并行执行,而它实际上是在无序执行方案中创建完美的依赖关系链。 INC指令修改一个寄存器。 CMP指令必须等待INC完成以检查该寄存器中的值并相应地修改标志。然后,条件跳转指令必须等待CMP写入标志以决定是否实际跳转。恐怕没有平行化。更不要说跳跃会导致流水线延迟 - 分支预测器会处理这一点。 – 2012-04-03 11:13:39
更不用说,INC指令必须等待修改标志的先前指令才能保持CF标志的状态。只需将INC替换为相应的ADD即可解决该问题。 – 2012-04-03 11:18:43