DSP的内部结构具有特殊性,如果程序的编写能够在一定程度上与DSP的结构相适应,利用C编译器的优化功能就可以在程序级的基础上大大提高算法的性能[1~5]。具体如下:
(1) 尽量使用int 类型中间变量
在图像处理程序中,数据通常为8bit的变量,而C6XDSP的内部寄存器和数据通道为32bit,在图像处理的滤波、卷积等过程中,需要较多的中间变量,如果采用8bit的中间变量存储方式,势必迫使编译器使用额外的数据调整指令,因此采用32bit的中间变量具有最好的效率。
(2) 使用移位代替除法运算
①DSP中的移位运算具有硬件支持,由一条指令完成,而除法运算使用程序实现,比较复杂耗时。
②DSP的浮点运算往往采用调用子函数方法,效率低,编译器无法进行软件流水优化,而采用移位运算可以代替一些定值的浮点运算,如:
for(I=0;I<1000;I++) for(I=0;I<1000;I++)
{ {
b+=35*0.325; b+=((35*333)>>10);
} }
以上两条程序完成同样的功能,但是第一条含有浮点数的乘法,运行时间为222 775;第二条只有定点数的乘法和移位运算,运行时间仅为881,可见效率差距非常大。
(3) 使用C6x内联函数
C6x编译器内部提供了许多指令,可以在单周期内完成许多复杂的函数功能,从而提高了代码的运行速度。如:
“饱和加法”:
int sadd(int a, int b)
{
int result;
result = a + b;
if (((a ^ b) & 0x80000000) == 0)
{
if ((result ^ a) & 0x80000000)
{
result = (a < 0) ? 0x80000000 : 0x7fffffff;
}
}
return (result);
}
这样一个复杂的算法可以用一条内联函数_sadd( a,b)实现。
(4) 使用32bit数据类型访问16bit数据
由于C6x系列DSP具有32位的寄存器和内部通道,在操作16bit的数据如short类型时,往往浪费一半的寄存器空间和通道带宽。为了充分利用这些资源,编译器设置了一些内联函数同时对两个16bit的数据进行操作,如_add2、_sub2。
short in1[]、 short in2[] 是两个short类型的数组,具有N项,以下是将两者对应项相加的操作。
for (i = 0; i < (N/2); i++)
_amem4(&sum[i]) = add2(_amem4_const(&in1[i]), _amem4_const(&in2[i]));
程序中,_amem4_const、_amem4将in1、in2和sum进行了32bit的对齐操作,然后同时进行两个short数据的加法运算和读取存储运算,从而提高了运算效率。
(5) 使用restrict关键字消除内存关联
为提高代码效率,C6x编译器总是尽可能多地安排指令并行,而指令能否并行取决于指令之间的相关性。对于编译器而言,内存读写指令之间是否独立是很难判断的,如下列程序:
void vecsum(short *sum, short *in1, short *in2, unsigned int N)
{
int i;
for (i = 0; i < N; i++)
sum[i] = in1[i] + in2[i];
}
程序中,sum的存储对于in1、in2的读取地址产生影响,只有等到sum存储完毕以后,才可以再次进行in1、in2的读取操作。这个问题叫做“别名问题”,因为sum可能和in1是一个地址,使其无法将读取数据和写结果的操作并行起来。
为了让编译器放心地将读取源数据和写结果数据两者并行操作,可以利用restrict变量申明当前数组名(或指针)是指向这段内存的惟一变量,如下列程序所示:
void vecsum(short *sum, short * restrict in1, short * restrict in2, unsigned int N)
{
int i;
for (i = 0; i < N; i++)
sum[i] = in1[i] + in2[i];
}
这样就可以消除以上的内存相关性,提高流水线效率。 |