DSP编译器优化

(1)           使用限定词restrict.在函数调用时,参数中经常出现多个指针变量,C编译器不能确定他们是否相关,因而默认他们具有存储器相关性,指令只能串联执行。如使用restrict限定词可消除指针指向不定的现象,从而使指令并行执行成为可能。

(2)           用预处理指令#pragma向C编译器传输信息,比如通过#pragma MUST_ITERATE()向C编译器传递loop count信息(如最小迭代次数、最多迭代次数以及迭代次数因子)。当loop count中的最小迭代次数大于最小安全迭代次数时,C编译器将可能执行软件流水;此外,迭代次数因子可通报C编译器loop count是偶数还是奇数,如loop count是偶数,C编译器将会按照尽可能多的指令并行执行原则展开循环,实现不同程度的软件流水。

(3)           使用选项 -k ,令编译器保留.asm文件,因为编译器产生的.asm文件反馈了许多信息,理解这些信息,按它的提示修改C语言程序,对尽快优化代码有好处.。-s:汇编语言文件中加入优化信息,如果没有则加入C语言源程序作为注释。
-mw:在汇编语言文件加入软件流水线信息。

(4)           联合使用-pm与-o3编译选项。在使用-o3选项时,尽量联合使用-pm。-pm是程序级优化,在程序级优化中,所有源文件都被编译到一个中间文件中,编译器在编译时可以从整个程序的角度来观察。一旦编译器确定两个指针不会访问同一存储器地址,它就会进行相应的优化。

(5)           使用-mt选项。-mt选项是向编译器说明在代码中没有使用混叠技术,可以更积极地优化,若使用了混合编程则不选此选项。

(6)           建议使用的编译方式
Cl6x -gk -mt -o3 -mw -ss  “filename”
方式1用于程序的调试,这种方式具有比较强的优化能力,并且支持符号调试。在编译的过程中不会发生错误。
由于生成的“out”文件中包含了符号信息和行号信息,所以比较大。
Cl6x -k -mgt -o3 -mw -ss  “filename”
方式2用于程序的剖析(profile),这种方式的优化能力几乎最强(绝大多数情况下与方式3相同),
并且支持对程序进行profile。文件中只包含了符号信息和很少的行号信息,所以“out”文件比较小。
Cl6x -k -mt -o3 -mw -ss  “filename”
方式3用于最终的发行版本程序,可以对程序进行最强的优化,并且去掉了全部的符号和行号信息,所以“out”文件比较小。
由多个文件组成的程序应该编写makefile,将编译参数放在该文件中,并在其中说明使用的编译器的版本号。

 

DSP程序代码优化

一.数据类型

1.整数。

尽量使用无符号型整数代替整型,在某些处理器中,对无符号整型的处理要比对整型的处理快。如果知道一个变量的值不可能为负数,最好定义为无符号数。

如 unsigned int    variable1;

   unsigned  short int variable2;

2.全局变量

程序在运行时不会为全局变量分配寄存器,在一个函数内,如果要使用某个全局变量,最好先定义一个局部变量,在把该全局变量的值赋给局部变量。

3. 数组和指针

   一维数组的比多维数组效率高。

4.对于定点乘法输入,尽可能使用short型数据,因为该类型提供了C6000 16位乘法器的最有效使用。

5.浮点乘法计算

   在定点处理器,对定点数据和浮点数据的处理能力相差几十倍,所以用户程序中若要进行浮点运算,应采用Q格式数进行处理。Q格式数说明参看TI相关文档。

二.循环体优化

    一般来说,一个程序消耗时间最长的部分位于一个循环体内,而且这个循环体的循环次数非常之多。对循环体的优化注意以下几个方面:

1.循环结束条件

     一个复杂的循环结束条件可能给程序带来额外的开销,因此循环结束条件越简单越好。

2.循环体内尽量少用条件判断

循环体内最好不要有条件判断句,尤其是在内层循环,因为内层循环太多的条件判断句会打断程序执行的流水线。

3.while和for循环的选取

如果知道循环次数的话,选用for语句。

4.循环体内,尽量不要使用系统函数调用,因为系统调用会消耗太多时间。

5.如果有几层循环的话,将循环次数多的放在内层循环里。

6.在循环体内最好不要用数学函数,尤其是幂运算、除法运算、开方运算等。

7.循环阻塞

   如果一个循环体内执行的语句过多,循环一次的指令数可能超出了处理器指令缓冲器的大小,导致效率降低。可以把一个大的循环拆成两个循环。

8.循环展开

   对于多层循环来说,优化的唯一方法就是将循环展开,不管是展开外层循环还是展开内层循环,结果就是变成单循环。这样的话,编译器就可以优化这个循环了。

     循环展开最关键的是要看看哪层循环次数最小,就展开这层循环。如下面这个双层循环:

    for(i=0;i<40;i++)
{

for(j=0;j<16;j++)

sum +=coefs[j]*input[I + 15 -j];

out[i] = (sum >> 15);
}

可以看出,内层循环的循环次数很少,运算量也不大,每个循环只占用了一个M单元用来进行乘法运算,浪费了资源,我们可以内层循环展开来执行如下:

for(i=0;i<40;i++)
{

sum +=coefs[0]*input[i+15];

sum +=coefs[1]*input[i+14];

sum +=coefs[2]*input[i+13];

sum +=coefs[3]*input[i+12];

sum +=coefs[4]*input[i+11];

sum +=coefs[5]*input[i+10];

sum +=coefs[6]*input[i+9];

sum +=coefs[7]*input[i+8];

sum +=coefs[8]*input[i+7];

sum +=coefs[9]*input[i+6];

sum +=coefs[10]*input[i+5];

sum +=coefs[11]*input[i+4];

sum +=coefs[12]*input[i+3];

sum +=coefs[13]*input[i+2];

sum +=coefs[14]*input[i+1];

sum +=coefs[15]*input[i+0];

}

    这样的话,双循环就变成了单循环,而且在每个循环里使用了两个乘法单元进行乘法运算,明显提高了效率。

9.对循环计数器应使用int或者unsigned int型,不要使用16位或者8位的数据类型,以避免不必要的符号扩展指令。

三函数调用

1.    inline函数。

对于调用比较频繁的函数,可以定义为内联函数,在定义时加关键字inline,可以减少函数调用的开销

2.函数的指针参数

如果函数的参数有指针参数,并且这个指针的值在函数被调用期间被使用很多次,而且每次使用并不会改变指针参数所指向的变量的值,那么在函数内部将这个指针参数的值赋给一个局部变量,这样能减少函数每次去读取指针参数所指向的值的时间。

3 函数调用的开销

    函数调用的开销是比较小的,相对于被调用函数的执行时间来说,函数调用的开销所占的比例很小,但是对于函数的参数想要以寄存器的形式传递的话,那么所能传递的参数是有一些上限的,通常以寄存器形式传递的函数参数都要是整数类型或者是占用内存为4个整型大小的结构体变量,假如参数个数的上限是4的话,第5个参数以及后来的其他参数都将会存储在栈上,这样调用时就增加了时间。

4.避免使用函数的递归调用

    尽管递归函数使代码看起来更简洁,但是递归函数会带来很多额外的开销。

四.运算

1.用移位运算代替乘法运算

2.尽量使用乘法运算代替除法运算

 

 

五指针

1.指针链

在访问结构体内部变量时,经常用到指针链。如果指针链很长,可以定义一些中间变量缩短指针链,加快访问速度。

如:typedef struct {intx,y,z;} Point3;

    Typedef  struct{Point3 *pos,*direction;} Object;

VoidInitPos1(Object *p){

p->pos->x =0;

p->pos->y =0;

p->pos->z =0;

}

可以优化为

VoidInitPos1(Object *p){

Point3 *pos =p->pos;

pos->x = 0;

pos->y = 0;

pos->z = 0;

}

六.指令的并行性和流水线技术

1.减小存储器相关性

为使指令达到最大效率,C64X编译器尽可能将指令安排为并行执行。为使指令并行操作,编译器必须知道指令间的关系,因为只有不相关的指令才可以并行执行。当编译器不能确定两条指令是否相关时,则编译器假定它们是相关的,从而不能并行执行。设计中常采用关键字const来指定目标,const表示一个变量或一个变量的存储单元保持不变。因此,在代码中加入关键字const,可以去除指令间的相关性。例如下面的程序:

Void vecsum(short *sum, short*in1,short *in2,unsignedint N)

{

    Int I;

For(i= 0; i<N;i++)

Sum [i]= in1[i] + in2[i];

}

写sum可能对指针in1、in2所指向的地址有影响,从而in1和in2的读操作必须等到sum 操作完成之后才能进行,降低了流水效率,为帮助编译器确定存储器的相关性,使用const关键字来指定一个目标,上面的源程序可改为含关键字const的优化源代码:

Void vecsum(short *sum, const short*in1,const short*in2,unsigned int N)

{

    Int I;

For(i= 0; i<N;i++)

Sum [i]= in1[i] + in2[i];

}

2.软件流水线技术的使用

软件流水线技术用来对一个循环结构的指令进行调度安排,使之成为多重迭代循环并行执行。在编译代码时,可以选择编译器的-o2或-o3选项,则编译器将根据程序尽可能低地安排软件流水线。

在DSP算法中存在大量的循环操作,因此充分地运用软件流水线方式,能极大地提高程序的运行速度。

能进行软件流水的循环必须满足以下条件:

(1).循环中除了能包含内联函数外,不能有其他函数调用

 (2).循环中不能有终止的条件

(3)循环计数器必须是递减并且在0时终止,使用-o2 and -o3选项的一个原因就是将尽可能多的循环转换成递减计数的循环.

(4).循环体内不能修改循环计数器

 (5)循环体内不能有复杂的条件判断代码.

 

七  使用片上内存和DMA

1把程序和经常要用的数据放入片内RAM; 片内RAM与CPU; 工作在同一时钟频率,比片外RAM性能高得多。因此把程序放在片内可以大大提高运行的速度。同时对于一些经常要用到的数据,放入片内,也会节省处理时间。

 2.通过DMA技术搬移数据; 对于C64X芯片,其片内RAM有1MB,但是对于一些大型的图像处理算法而言,仍可能是不够的,因此经常通过DMA技术,把需要用到的数据搬入片内,把不需要的搬到片外,可以大大的提高程序的运行速度。

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值