DSP程序优化总结文档格式.docx

上传人:b****1 文档编号:426098 上传时间:2023-04-28 格式:DOCX 页数:28 大小:26.96KB
下载 相关 举报
DSP程序优化总结文档格式.docx_第1页
第1页 / 共28页
DSP程序优化总结文档格式.docx_第2页
第2页 / 共28页
DSP程序优化总结文档格式.docx_第3页
第3页 / 共28页
DSP程序优化总结文档格式.docx_第4页
第4页 / 共28页
DSP程序优化总结文档格式.docx_第5页
第5页 / 共28页
DSP程序优化总结文档格式.docx_第6页
第6页 / 共28页
DSP程序优化总结文档格式.docx_第7页
第7页 / 共28页
DSP程序优化总结文档格式.docx_第8页
第8页 / 共28页
DSP程序优化总结文档格式.docx_第9页
第9页 / 共28页
DSP程序优化总结文档格式.docx_第10页
第10页 / 共28页
DSP程序优化总结文档格式.docx_第11页
第11页 / 共28页
DSP程序优化总结文档格式.docx_第12页
第12页 / 共28页
DSP程序优化总结文档格式.docx_第13页
第13页 / 共28页
DSP程序优化总结文档格式.docx_第14页
第14页 / 共28页
DSP程序优化总结文档格式.docx_第15页
第15页 / 共28页
DSP程序优化总结文档格式.docx_第16页
第16页 / 共28页
DSP程序优化总结文档格式.docx_第17页
第17页 / 共28页
DSP程序优化总结文档格式.docx_第18页
第18页 / 共28页
DSP程序优化总结文档格式.docx_第19页
第19页 / 共28页
DSP程序优化总结文档格式.docx_第20页
第20页 / 共28页
亲,该文档总共28页,到这儿已超出免费预览范围,如果喜欢就下载吧!
下载资源
资源描述

DSP程序优化总结文档格式.docx

《DSP程序优化总结文档格式.docx》由会员分享,可在线阅读,更多相关《DSP程序优化总结文档格式.docx(28页珍藏版)》请在冰点文库上搜索。

DSP程序优化总结文档格式.docx

{

if(i==0)

return1;

else

returni*factorial(i-1);

}

新代码:

staticlongfactorial_table[]=

{1,1,2,6,24,120,720/*etc*/};

returnfactorial_table[i];

如果表很大,不好写,就写一个init函数,在循环外临时生成表格。

(2)、求余运算

a=a%8;

可以改为:

a=a&

7;

说明:

位操作只需一个指令周期即可完成,而大部分的C编译器的“%”运算均是调用子程序来完成,代码长、执行速度慢。

通常,只要求是求2n方的余数,均可使用位操作的方法来代替。

(3)、平方运算

a=pow(a,2.0);

a=a*a;

在有内置硬件乘法器的单片机中(如51系列),乘法运算比求平方运算快得多,因为浮点数的求平方是通过调用子程序来实现的,在自带硬件乘法器的AVR单片机中,如ATMega163中,乘法运算只需2个时钟周期就可以完成。

既使是在没有内置硬件乘法器的AVR单片机中,乘法运算的子程序比平方运算的子程序代码短,执行速度快。

如果是求3次方,如:

a=pow(a,3。

0);

更改为:

a=a*a*a;

则效率的改善更明显。

(4)、用移位实现乘除法运算

a=a*4;

b=b/4;

a=a<

<

2;

b=b>

>

通常如果需要乘以或除以2n,都可以用移位的方法代替。

在ICCAVR中,如果乘以2n,都可以生成左移的代码,而乘以其它的整数或除以任何数,均调用乘除法子程序。

用移位的方法得到代码比调用乘除法子程序生成的代码效率高。

实际上,只要是乘以或除以一个整数,均可以用移位的方法得到结果,如:

a=a*9

a=(a<

3)+a

采用运算量更小的表达式替换原来的表达式,下面是一个经典例子:

旧代码:

x=w%8;

y=pow(x,2.0);

z=y*33;

for(i=0;

i<

MAX;

i++)

h=14*i;

printf("

%d"

,h);

新代码:

x=w&

7;

/*位操作比求余运算快*/

y=x*x;

/*乘法比平方运算快*/

z=(y<

5)+y;

/*位移乘法比乘法快*/

for(i=h=0;

i<

i++)

h+=14;

/*加法比乘法快*/

}

(5)、避免不必要的整数除法

  整数除法是整数运算中最慢的,所以应该尽可能避免。

一种可能减少整数除法的地方是连除,这里除法可以由乘法代替。

这个替换的副作用是有可能在算乘积时会溢出,所以只能在一定范围的除法中使用。

  不好的代码:

inti,j,k,m;

m=i/j/k;

推荐的代码:

m=i/(j*k);

(6)、使用增量和减量操作符

在使用到加一和减一操作时尽量使用增量和减量操作符,因为增量符语句比赋值语句更快,原因在于对大多数CPU来说,对内存字的增、减量操作不必明显地使用取内存和写内存的指令,比如下面这条语句:

x=x+1;

模仿大多数微机汇编语言为例,产生的代码类似于:

moveA,x;

把x从内存取出存入累加器A

addA,1;

累加器A加1

storex;

把新值存回x

如果使用增量操作符,生成的代码如下:

incrx;

x加1

显然,不用取指令和存指令,增、减量操作执行的速度加快,同时长度也缩短了。

(7)、使用复合赋值表达式

复合赋值表达式(如a-=1及a+=1等)都能够生成高质量的程序代码。

(8)、提取公共的子表达式

在某些情况下,C++编译器不能从浮点表达式中提出公共的子表达式,因为这意味着相当于对表达式重新排序。

需要特别指出的是,编译器在提取公共子表达式前不能按照代数的等价关系重新安排表达式。

这时,程序员要手动地提出公共的子表达式(在VC.NET里有一项“全局优化”选项可以完成此工作,但效果就不得而知了)。

不好的代码:

floata,b,c,d,e,f;

e=b*c/d;

f=b/d*a;

推荐的代码:

constfloatt(b/d);

e=c*t;

f=a*t;

 

floata,b,c,e,f;

e=a/c;

f=b/c;

constfloatt(1.0f/c);

e=a*t;

f=b*t;

4、结构体成员的布局

  很多编译器有“使结构体字,双字或四字对齐”的选项。

但是,还是需要改善结构体成员的对齐,有些编译器可能分配给结构体成员空间的顺序与他们声明的不同。

但是,有些编译器并不提供这些功能,或者效果不好。

所以,要在付出最少代价的情况下实现最好的结构体和结构体成员对齐,建议采取下列方法:

(1)按数据类型的长度排序

把结构体的成员按照它们的类型长度排序,声明成员时把长的类型放在短的前面。

编译器要求把长型数据类型存放在偶数地址边界。

在申明一个复杂的数据类型(既有多字节数据又有单字节数据)时,应该首先存放多字节数据,然后再存放单字节数据,这样可以避免内存的空洞。

编译器自动地把结构的实例对齐在内存的偶数边界。

(2)把结构体填充成最长类型长度的整倍数

把结构体填充成最长类型长度的整倍数。

照这样,如果结构体的第一个成员对齐了,所有整个结构体自然也就对齐了。

下面的例子演示了如何对结构体成员进行重新排序:

不好的代码,普通顺序:

struct

{

  chara[5];

  longk;

 doublex;

}baz;

推荐的代码,新的顺序并手动填充了几个字节:

  doublex;

  longk;

charpad[7];

这个规则同样适用于类的成员的布局。

(3)按数据类型的长度排序本地变量

当编译器分配给本地变量空间时,它们的顺序和它们在源代码中声明的顺序一样,和上一条规则一样,应该把长的变量放在短的变量前面。

如果第一个变量对齐了,其它变量就会连续的存放,而且不用填充字节自然就会对齐。

有些编译器在分配变量时不会自动改变变量顺序,有些编译器不能产生4字节对齐的栈,所以4字节可能不对齐。

下面这个例子演示了本地变量声明的重新排序:

  不好的代码,普通顺序

shortga,gu,gi;

longfoo,bar;

doublex,y,z[3];

chara,b;

floatbaz;

推荐的代码,改进的顺序

doublez[3];

doublex,y;

(4)把频繁使用的指针型参数拷贝到本地变量

避免在函数中频繁使用指针型参数指向的值。

因为编译器不知道指针之间是否存在冲突,所以指针型参数往往不能被编译器优化。

这样数据不能被存放在寄存器中,而且明显地占用了内存带宽。

注意,很多编译器有“假设不冲突”优化开关(在VC里必须手动添加编译器命令行/Oa或/Ow),这允许编译器假设两个不同的指针总是有不同的内容,这样就不用把指针型参数保存到本地变量。

否则,请在函数一开始把指针指向的数据保存到本地变量。

如果需要的话,在函数结束前拷贝回去。

//假设q!

=r

voidisqrt(unsignedlonga,unsignedlong*q,unsignedlong*r)

  *q=a;

  if(a>

0)

  {

    while(*q>

(*r=a/*q))

    {

      *q=(*q+*r)>

1;

    }

  }

  *r=a-*q**q;

  unsignedlongqq,rr;

  qq=a;

    while(qq>

(rr=a/qq))

      qq=(qq+rr)>

  rr=a-qq*qq;

  *q=qq;

  *r=rr;

5、循环优化

(1)、充分分解小的循环

  要充分利用CPU的指令缓存,就要充分分解小的循环。

特别是当循环体本身很小的时候,分解循环可以提高性能。

注意:

很多编译器并不能自动分解循环。

不好的代码:

//3D转化:

把矢量V和4x4矩阵M相乘

for(i=0;

4;

i++)

  r[i]=0;

  for(j=0;

j<

j++)

    r[i]+=M[j][i]*V[j];

r[0]=M[0][0]*V[0]+M[1][0]*V[1]+M[2][0]*V[2]+M[3][0]*V[3];

r[1]=M[0][1]*V[0]+M[1][1]*V[1]+M[2][1]*V[2]+M[3][1]*V[3];

r[2]=M[0][2]*V[0]+M[1][2]*V[1]+M[2][2]*V[2]+M[3][2]*V[3];

r[3]=M[0][3]*V[0]+M[1][3]*V[1]+M[2][3]*V[2]+M[3][3]*v[3];

(2)、提取公共部分

对于一些不需要循环变量参加运算的任务可以把它们放到循环外面,这里的任务包括表达式、函数的调用、指针运算、数组访问等,应该将没有必要执行多次的操作全部集合在一起,放到一个init的初始化程序中进行。

(3)、延时函数

通常使用的延时函数均采用自加的形式:

voiddelay(void)

unsignedinti;

for(i=0;

i<

1000;

i++);

将其改为自减延时函数:

for(i=1000;

i>

0;

i--);

两个函数的延时效果相似,但几乎所有的C编译对后一种函数生成的代码均比前一种代码少1~3个字节,因为几乎所有的MCU均有为0转移的指令,采用后一种方式能够生成这类指令。

在使用while循环时也一样,使用自减指令控制循环会比使用自加指令控制循环生成的代码更少1~3个字母。

但是在循环中有通过循环变量“i”读写数组的指令时,使用预减循环有可能使数组超界,要引起注意。

(4)、while循环和do…while循环

用while循环时有以下两种循环形式:

i=0;

while(i<

1000)

i++;

//用户程序

或:

i=1000;

do

i--;

//用户程序

while(i>

0);

在这两种循环中,使用do…while循环编译后生成的代码的长度短于while循环。

(5)、循环展开

这是经典的速度优化,但许多编译程序(如gcc-funroll-loops)能自动完成这个事,所以现在你自己来优化这个显得效果不明显。

for(i=0;

100;

do_stuff(i);

10;

可以看出,新代码里比较指令由100次降低为10次,循环时间节约了90%。

不过注意:

对于中间变量或结果被更改的循环,编译程序往往拒绝展开,(怕担责任呗),这时候就需要你自己来做展开工作了。

还有一点请注意,在有内部指令cache的CPU上(如MMX芯片),因为循环展开的代码很大,往往cache溢出,这时展开的代码会频繁地在CPU的cache和内存之间调来调去,又因为cache速度很高,所以此时循环展开反而会变慢。

还有就是循环展开会影响矢量运算优化。

(6)、循环嵌套

把相关循环放到一个循环里,也会加快速度。

i++)/*initialize2darrayto0'

s*/

for(j=0;

j++)

a[i][j]=0.0;

i++)/*put1'

salongthediagonal*/

a[i][i]=1.0;

/*put1'

(7)、Switch语句中根据发生频率来进行case排序

Switch可能转化成多种不同算法的代码。

其中最常见的是跳转表和比较链/树。

当switch用比较链的方式转化时,编译器会产生if-else-if的嵌套代码,并按照顺序进行比较,匹配时就跳转到满足条件的语句执行。

所以可以对case的值依照发生的可能性进行排序,把最有可能的放在第一位,这样可以提高性能。

此外,在case中推荐使用小的连续的整数,因为在这种情况下,所有的编译器都可以把switch转化成跳转表。

intdays_in_month,short_months,normal_months,long_months;

switch(days_in_month)

  case28:

  case29:

    short_months++;

    break;

  case30:

    normal_months++;

  case31:

    long_months++;

  default:

    cout<

"

monthhasfewerthan28ormorethan31days"

<

endl;

(8)、将大的switch语句转为嵌套switch语句

当switch语句中的case标号很多时,为了减少比较的次数,明智的做法是把大switch语句转为嵌套switch语句。

把发生频率高的case标号放在一个switch语句中,并且是嵌套switch语句的最外层,发生相对频率相对低的case标号放在另一个switch语句中。

比如,下面的程序段把相对发生频率低的情况放在缺省的case标号内。

pMsg=ReceiveMessage();

switch(pMsg->

type)

caseFREQUENT_MSG1:

handleFrequentMsg();

break;

caseFREQUENT_MSG2:

handleFrequentMsg2();

caseFREQUENT_MSGn:

handleFrequentMsgn();

default:

//嵌套部分用来处理不经常发生的消息

caseINFREQUENT_MSG1:

handleInfrequentMsg1();

caseINFREQUENT_MSG2:

handleInfrequentMsg2();

caseINFREQUENT_MSGm:

handleInfrequentMsgm();

如果switch中每一种情况下都有很多的工作要做,那么把整个switch语句用一个指向函数指针的表来替换会更加有效,比如下面的switch语句,有三种情况:

enumMsgType{Msg1,Msg2,Msg3}

switch(ReceiveMessage()

caseMsg1;

caseMsg2;

caseMsg3;

为了提高执行速度,用下面这段代码来替换这个上面的switch语句。

/*准备工作*/

inthandleMsg1(void);

inthandleMsg2(void);

inthandleMsg3(void);

/*创建一个函数指针数组*/

int(*MsgFunction[])()={handleMsg1,handleMsg2,handleMsg3};

/*用下面这行更有效的代码来替换switch语句*/

status=MsgFunction[ReceiveMessage()]();

(9)、循环转置

有些机器

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 自然科学 > 物理

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2