在“如何使用SSE指令提高FIR算法效率(进化一)“一文中,我们通过SHUFPS指令来完成行列向量之间的转化,实现了向量相加一次写操作的功能,很大程度的提高了程序的执行效率。
那么参考SSE/SEE2的指令,我们能否用其他方式来完成呢?
恩…,好像有,再想想…
哦,对了,MOVLHPS,MOVLHPS不是也可以吗?那么让我们看看如何实现吧J!
为了是大家能够看的清楚一些,代码中加入了部分的注释(很抱歉,在相关的前两篇文章中,我没有做这个方面的工作:-(,以后一定要加强)。
还是在看代码之前,先回顾一下问题:
我们要把A3+A2+A1+A0的值放入数组out[ 0 ]中,B3+B2+B1+B0的值放入out[ 1 ]中,依次类推直到out[ 3 ] = D3+D2+D1+D0。程序的前半段已经将它们存储到
xmm0=[A3, A2, A1, A0]
xmm1=[B3, B2, B1, B0]
xmm2=[C3, C2, C1, C0]
xmm3=[D3, D2, D1, D0]
因为它们是行相加而非是列相加,所以没有合适的SSE/SSE2指令直接使用。程序的下半段利用MOVLHPS,MOVHLPS和SHUFPS的组合实现了转换和向量相加,并存储于out数组中。
static void ShowFIR_O4( float *inPtr, float *outPtr, float *coeffPtr, unsigned int count ) { __asm { xorps xmm0, xmm0 xorps xmm1, xmm1 xorps xmm2, xmm2 xorps xmm3, xmm3 xor eax, eax xor ecx, ecx mov ebx, DWORD PTR[ coeffPtr ] mov esi, DWORD PTR[ inPtr ] mov edx, DWORD PTR[ outPtr ] jmp b2 b1: movaps xmm4, XMMWORD PTR[ ebx + ecx * 4 ]
movaps xmm5, XMMWORD PTR[ esi + ecx * 4 ] mulps xmm5, xmm4 addps xmm0, xmm5
movups xmm5, XMMWORD PTR[ esi + ecx * 4 + 4 ] mulps xmm5, xmm4 addps xmm1, xmm5
movups xmm5, XMMWORD PTR[ esi + ecx * 4 + 8 ] mulps xmm5, xmm4 addps xmm2, xmm5
movups xmm5, XMMWORD PTR[ esi + ecx * 4 + 12 ] mulps xmm5, xmm4 addps xmm3, xmm5
add ecx, 4 cmp ecx, TAP jb b1
// xmm0: A3, A2, A1, A0 // xmm1: B3, B2, B1, B0 // xmm2: C3, C2, C1, C0 // xmm0: D3, D2, D1, D0 movaps xmm4, xmm0 // xmm4: A3, A2, A1, A0 movlhps xmm4, xmm1 // xmm4: B1, B0, A1, A0 movaps xmm5, xmm1 // xmm5: B3, B2, B1, B0 movhlps xmm5, xmm0 // xmm5: B3, B2, A3, A2 addps xmm4, xmm5 // xmm4: B1+B3, B0+B2, A1+A3, A0+A2
movaps xmm6, xmm2 // xmm6: C3, C2, C1, C0 movlhps xmm6, xmm3 // xmm6: D1, D0, C1, C0 movaps xmm7, xmm3 // xmm7: D3, D2, D1, D0 movhlps xmm7, xmm2 // xmm7: D3, B2, C3, C2 addps xmm6, xmm7 // xmm6: D1+D3, D0+D2, C1+C3, C0+C2
movaps xmm5, xmm4 // xmm5: B1+B3, B0+B2, A1+A3, A0+A2 shufps xmm4, xmm6, 0x88 // xxm4: D0+D2, C0+C2, B0+B2, A0+A2 shufps xmm6, xmm5, 0xDD // xmm6: D1+D3, C1+C3, B1+B3, A1+A3 addps xmm4, xmm6 // xMM4: D0+D1+D2+D3, C0+C1+C2+C3, B0+B1+B2+B3, A0+A1+A2+A3
movaps XMMWORD PTR[ edx + eax * 4 ], xmm4
add eax, 4 b2: cmp eax, count - TAP jb b1 } } |
相比于ShowFIR_O3中的相关部分,这里的指令数明显少了很多(ShowFIR_O4有15条指令,ShowFIR_O3有31条指令),大概为其的一半。
那么我们是否会认为它的速度要快很多呢?
也许吧?!从理论上说,指令少总是要快一些的,但是ShowFIR_O4使用的MOVHLPS,MOVLHPS是数据相关的指令,它可能会受到这个方面的影响。不过,所有的猜想总没有测试来的直接。
请看下面数据:
在Intel Pentium-M2.2G上运行10000次后的计数值: 4: ShowFIR_O3 delta = 1475 5: ShowFIR_O4 delta = 1476
在AMD Anthlon 4600+上运行10000次后的计数值: 4: ShowFIR_O3 delta = (783, 0) 5: ShowFIR_O4 delta = (799, 0) |
从结果看,两者的性能差不多(个人观点,它们的结果会因为CPU框架不同产生一些或快或慢的结果,但是差别不会太大。)。ShowFIR_O3中的操作没有进行优化,如果在稍慢的情况下,能够通过再次优化来提速。
小结:
1. 本文主要目的是提供一个实现方法来开阔思路,在实现的同时也能够回顾和进一步熟练使用SSE/SSE2指令。
2. 它与ShowFIR_O3中的相关代码段可以作为一个模板供日后参考使用。
总之,它是对以前一篇学习笔记的再探讨,所有的东西并不是最佳答案,它将会在日后的学习中逐步完善。正如以前一贯的作风,希望网友们能够给我多找出一些岔子,以便我再学习再提高。谢谢了!