我的应用程序中有一个乘加内核,我想提高它的性能。
我使用 Intel Core i7-960(3.2 GHz 时钟)并且已经使用 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 add 需要 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>
...