您的位置:首页 > 其它

如何使用SSE指令提高FIR算法效率

2008-11-24 13:33 351 查看

如何使用SSE指令提高FIR算法效率

在搭建好VC6.0 SSE环境后(请参考:如何使VC6支持内嵌SSE指令),菜菜这里就开始现学现卖了J。因为自己的机器较老,仅支持MMX/SSE/SSE2指令,所以这里的方法也仅仅局限于此。

好的,言归正传!一般的FIR算法如下:

static void ShowFIR( float *in, float
*out, float *coeff, unsigned int count )
{
unsigned
int i;
unsigned
int j;

for
( i = 0; i < count - TAP; ++ i )
{
float
sum;

sum
= 0;
for
( j = 0; j < TAP; ++ j )
{
sum
+= in[ j ] * coeff[ j ];
}
*out
++ = sum;
++
in;
}
}
那么如何使用SSE指令来优化它呢

SSE的指令在于它使用了向量计算,极大的提高了运算速度,所以在优化之前,我们首先要对原有的c代码进行向量构造。因为水平有限,构造了一个粗糙向量(如果有那位网友能够指导我进行优化,我将万分感谢!)。

这里假设相关的所有长度都是16个字节的倍数。

static void ShowFIR_O1( float *in, float
*out, float *coeff, unsigned int count )
{
unsigned
int i;
unsigned
int j;

for
( i = 0; i < count - TAP; i += 4 )
{
out[
0 ] = 0;
out[
1 ] = 0;
out[
2 ] = 0;
out[
3 ] = 0;
for
( j = 0; j < TAP; j += 4 )
{
out[
0 ] += in[ j ] * coeff[ j ];
out[
0 ] += in[ j + 1 ] * coeff[ j + 1 ];
out[
0 ] += in[ j + 2 ] * coeff[ j + 2 ];
out[
0 ] += in[ j + 3 ] * coeff[ j + 3 ];

out[
1 ] += in[ j + 1 ] * coeff[ j ];
out[
1 ] += in[ j + 2 ] * coeff[ j + 1 ];
out[
1 ] += in[ j + 3 ] * coeff[ j + 2 ];
out[
1 ] += in[ j + 4 ] * coeff[ j + 3 ];

out[
2 ] += in[ j + 2 ] * coeff[ j ];
out[
2 ] += in[ j + 3 ] * coeff[ j + 1 ];
out[
2 ] += in[ j + 4 ] * coeff[ j + 2 ];
out[
2 ] += in[ j + 5 ] * coeff[ j + 3 ];

out[
3 ] += in[ j + 3 ] * coeff[ j ];
out[
3 ] += in[ j + 4 ] * coeff[ j + 1 ];
out[
3 ] += in[ j + 5 ] * coeff[ j + 2 ];
out[
3 ] += in[ j + 6 ] * coeff[ j + 3 ];
}

in += 4;
out
+= 4;
}
}
上面方法的主要思路是,在保持原有算法的基础上,对代码进行展开和重组,形成若干个向量组。在这个基础上,我们再使用SSE指令进行快速的矢量计算以获取高性能。从上的构建可以看出,我们有两组向量,分别用in和coeff指向。in指向的向量in[j]是16个字节地址对齐,而in[j+1], in[j+2], in[j+2]是不对齐的,故除了第一组能够使用movaps之外,其他都使用movups来装载。coeff仅使用一组向量和in的4组向量进行相乘,所以可以考虑将它放入xmm寄存器中提高运行效率。从上面的c代码可以看出,对于每组向量计算的结果将被放在4个单元中([127,96],
[95,64],[63:32], [31:0]),所以在计算最后要将它们水平相加后再放入out数组中。

相关的汇编代码如下:

static void ShowFIR_O2( 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

movhlps
xmm5, xmm0
addps
xmm5, xmm0
movaps
xmm6, xmm5
shufps
xmm5, xmm5, 1
addps
xmm5, xmm6
movss
XMMWORD PTR[ edx + eax * 4 ], xmm5

movhlps
xmm5, xmm1
addps
xmm5, xmm1
movaps
xmm6, xmm5
shufps
xmm5, xmm5, 1
addps
xmm5, xmm6
movss
XMMWORD PTR[ edx + eax * 4 + 4 ], xmm5

movhlps
xmm5, xmm2
addps
xmm5, xmm2
movaps
xmm6, xmm5
shufps
xmm5, xmm5, 1
addps
xmm5, xmm6
movss
XMMWORD PTR[ edx + eax * 4 + 8 ], xmm5

movhlps
xmm5, xmm3
addps
xmm5, xmm3
movaps
xmm6, xmm5
shufps
xmm5, xmm5, 1
addps
xmm5, xmm6
movss
XMMWORD PTR[ edx + eax * 4 + 12 ], xmm5

add
eax, 4
b2:
cmp
eax, count - TAP
jb
b1
}
}
为了防止与汇编保留字冲突,我将原有的C代码中的部分变量进行了重新命名。此外,汇编代码假设他们的首地址都是16字节对齐的。16字节地址对齐的方法很简单,只要在变相声明的前面加上“__declspec(
align( 16 ) )”修饰,比如:

static __declspec( align( 16 ) ) float
fin[ COUNT ];
static __declspec( align( 16 ) ) float
fout1[ COUNT ];
static __declspec( align( 16 ) ) float
fout2[ COUNT ];
static __declspec( align( 16 ) ) float
fcoeff[ TAP ];
它告诉编译器,上面声明的变量必须16字节地址对齐J。

小结

上面的汇编代码有几个较大的缺陷:

1. 没有对水平相加进行优化,SSE3有一个HADDPS的水平相加指令,它对性能的提升有重大作用,因为我现在无法调试(计算机仅仅支持SSE2)所以使用了上面一对难看的指令。

2. 没有对out向量进行一次向量写操作(在这个实现中,用了很傻的4次标量写操作)。主要原因是还没有充分理解所有相关的SSE指令,并将它们融会贯通。

3. 对原有代码的向量构造不够好,我想一定有一种方法可以避免最后的差劲的水平向量加操作和4次标量写操作。我的理想状态是没有水平向量相加和一次向量写操作。

我想,随着学习的深入,这些方面将会被逐一解决。我将在后继的文章中对这些遗憾的方面进行逐步探讨以获取更好的性能。如果有大侠能够出手相救,给我醍醐灌顶的提示,小弟在此叩拜了J!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: