C程序优化方案.docx

上传人:scccc 文档编号:13616722 上传时间:2022-01-20 格式:DOCX 页数:11 大小:31.88KB
返回 下载 相关 举报
C程序优化方案.docx_第1页
第1页 / 共11页
C程序优化方案.docx_第2页
第2页 / 共11页
C程序优化方案.docx_第3页
第3页 / 共11页
C程序优化方案.docx_第4页
第4页 / 共11页
C程序优化方案.docx_第5页
第5页 / 共11页
点击查看更多>>
资源描述

《C程序优化方案.docx》由会员分享,可在线阅读,更多相关《C程序优化方案.docx(11页珍藏版)》请在三一文库上搜索。

1、C代码优化方案1、选择合适的算法和数据结构选择一种合适的数据结构很重要,如果在一堆随机存放的数中使用了大量的插入和删除指令,那使用链表要快得多。数组与指针语句具有十分密切的关系,一般来说,指针比较灵活简洁,而数组则比较直观,容易理解。对于大部分的编译器,使用指针比使用数组生成的代码更短,执行效率更高。在许多种情况下,可以用指针运算代替数组索引,这样做常常能产生又快又短的代码。与数组索引相比,指针一般能使代码速度更快,占用空间更少。使用多维数组时差异更明显。下 面的代码作用是相同的,但是效率不一样?数组索引指针运算For(;)p=arrayA=arrayt+; for(;)a=*(p+);ooo

2、ooooooo o o o o o)指针方法的优点是,array的地址每次装入地址p后,在每次循环中只需对p增量操作。在数组索引方法中,每次循环中都必须根据t值求数组下标的复杂运算。2、使用尽量小的数据类型能够使用字符型(char)定义的变量,就不要使用整型(int)变量来定义;能够使用整型变量定义的变量就不要用长整型(long int),能不使用浮点型(float)变量就不要使用浮点型变量。当然,在定义变量后不要超过变量的作用范围,如果超过变量的范围赋值,C编译器并不报错,但程序运行结果却错了,而且这样的错误很难发现。在ICCAVR中,可以在 Options 中设定使用printf 参数,尽

3、量使用基本型参数 (%c、d %x %X %uD%s格式说明符),少用长整型参数(ld、lu、伙和区格式说明 符),至于浮点型的参数(f)则尽量不要使用,其它C编译器也一样。在其它条件不变的情况下,使用%f参数,会使生成的代码的数量增加很多,执行速度降低。3、减少运算的强度(1)、查表(游戏程序员必修课)一个聪明的游戏大虾,基本上不会在自己的主循环里搞什么运算工作,绝对是先计算好了,再到循环里查表。看下面的例子:旧代码:long factorial(int i)if (i = 0)return 1;elsereturn i * factorial(i - 1);)新代码:static long

4、 factorial_table=1, 1 , 2 , 6 , 24 , 120 , 720 /* etc */ ;long factorial(int i)return factorial_tablei; 如果表很大,不好写,就写一个init函数,在循环外临时生成表格。(2)、求余运算a=a%8;可以改为:a=a&7;说明:位操作只需一个指令周期即可完成,而大部分的C编译器的 %运算均是调用子程序来完成,代码长、执行速度慢。通常,只要求是求 2n方的余数,均可使用位操作的方法 来代替。(3)、平方运算a=pow(a,;可以改为:a=a*a;说明:在有内置硬件乘法器的单片机中(如51系列),乘

5、法运算比求平方运算快得多,因为浮点数的求平方是通过调用子程序来实现的,在自带硬件乘法器的AVR单片机中,如ATMega163中,乘法运算只需2个时钟周期就可以完成。既使是在没有内置硬件乘法器的AVR单片机中,乘法运算的子程序比平方运算的子程序代码短,执行速度快。如果是求3次方,如:a=pow(a, 3。0);更改为:a=a*a*a ;则效率的改善更明显。(4)、用移位实现乘除法运算a=a*4;b=b/4;可以改为:a=a2;通常如果需要乘以或除以2n,都可以用移位的方法代替。在ICCAVR中,如果乘以2n,都可以生成左移的代码,而乘以其它的整数或除以任何数,均调用乘除法子程序。用移位的方法得到

6、代码比调用乘除法子程序生成的代码效率高。实际上,只要是乘以或除以一个整数,均可以用移位的方法得到结果,如:a=a*9可以改为:a=(a3)+a采用运算量更小的表达式替换原来的表达式,下面是一个经典例子 旧代码:x = w % 8;y = pow(x ,;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 MAX; i+)

7、 (h += 14;/*printf(%d, h);)(5)、避免不必要的整数除法整数除法是整数运算中最慢的,所以应该尽可能避免。一种可能减少整数除法的地方是连除, 这里除法可以由乘法代替。这个替换的副作用是有可能在算乘积时会溢出,所以只能在一定范围的除法中使用。不好的代码:int i , j , k , m;m = i / j / k ;推荐的代码:int i , j , k , m;m = i / (j * k) ;(6)、使用增量和减量操作符在使用到加一和减一操作时尽量使用增量和减量操作符,因为增量符语句比赋值语句更快, 原因在于对大多数 CP阴说,对内存字的增、减量操作不必明显地使用取

8、内存和写内存的指 令,比如下面这条语句:x=x+1;模仿大多数微机汇编语言为例,产生的代码类似于:move A, x ; 把x从内存取出存入累加器 Aadd A, 1; 累加器A加1store x ;把新值存回 x如果使用增量操作符,生成的代码如下:incr x ;x力口 1显然,不用取指令和存指令,增、减量操作执行的速度加快,同时长度也缩短了。(7)、使用复合赋值表达式复合赋值表达式(如a-=1及a+=1等)都能够生成高质量的程序代码。(8)、提取公共的子表达式在某些情况下,C+编译器不能从浮点表达式中提出公共的子表达式,因为这意味着相当于 对表达式重新排序。需要特别指出的是,编译器在提取公

9、共子表达式前不能按照代数的等价 关系重新安排表达式。这时,程序员要手动地提出公共的子表达式(在里有一项“全局优化”选项可以完成此工作,但效果就不得而知了)。不好的代码:float a ,b,c,d,e,f;e = b * c / d ;f = b / d * a ;推荐的代码:float a ,b,c,d,e,f;const float t(b / d) ;e = c * t ;f = a * t ;不好的代码:float a , b , c , e , f ;o o oe = a / c ;f = b / c ;推荐的代码:float a , b , c , e , f ;const flo

10、at t(1.0f / c);e = a * t ;f = b * t ;4、结构体成员的布局很多编译器有“使结构体字,双字或四字对齐”的选项。但是,还是需要改善结构体成员的对齐,有些编译器可能分配给结构体成员空间的顺序与他们声明的不同。但是,有些编译器并不提供这些功能,或者效果不好。所以,要在付出最少代价的情况下实现最好的结构体和 结构体成员对齐,建议采取下列方法:(1)按数据类型的长度排序把结构体的成员按照它们的类型长度排序,声明成员时把长的类型放在短的前面。编译器要求把长型数据类型存放在偶数地址边界。在申明一个复杂的数据类型(既有多字节数据又有单字节数据)时,应该首先存放多字节数据,然后

11、再存放单字节数据,这样可以避免内存的空洞。编译器自动地把结构的实例对齐在内存的偶数边界。(2)把结构体填充成最长类型长度的整倍数把结构体填充成最长类型长度的整倍数。照这样,如果结构体的第一个成员对齐了,所有整个结构体自然也就对齐了。下面的例子演示了如何对结构体成员进行重新排序:不好的代码,普通顺序: struct(char a5;long k ;double x ; baz ;推荐的代码,新的顺序并手动填充了几个字节:struct(double xlong kchar a5;char pad7; baz这个规则同样适用于类的成员的布局。(3)按数据类型的长度排序本地变量当编译器分配给本地变量空

12、间时,它们的顺序和它们在源代码中声明的顺序一样,和上一条规则一样,应该把长的变量放在短的变量前面。如果第一个变量对齐了,其它变量就会连续的存放,而且不用填充字节自然就会对齐。有些编译器在分配变量时不会自动改变变量顺序,有些编译器不能产生 4字节对齐的栈,所以4字节可能不对齐。下面这个例子演示了本地变 量声明的重新排序:不好的代码,普通顺序short ga , gu , gi ;long foo , bar ;double x , y , z3;char a , b ;float baz ;推荐的代码,改进的顺序double z3;double x , y ;long foo , bar ;fl

13、oat bazshort ga , gu , gi ;(4)把频繁使用的指针型参数拷贝到本地变量避免在函数中频繁使用指针型参数指向的值。因为编译器不知道指针之间是否存在冲突,所以指针型参数往往不能被编译器优化。这样数据不能被存放在寄存器中,而且明显地占用了内存带宽。注意,很多编译器有“假设不冲突”优化开关(在VC里必须手动添加编译器命令彳/02或/00),这允许编译器假设两个不同的指针总是有不同的内容,这样就不用把指针型参数保存到本地变量。否则,请在函数一开始把指针指向的数据保存到本地变量。如果需要的话,在函数结束前拷贝回去。不好的代码:0.0f66M )指针方法的优点是,array的地址每次

14、装入地址 p后,在每次循环中只需对 p增量操作。在 数组索引方法中,每次循环中都必须进行基于r值求数组下标的复杂运算。使用宏函数而不是函数。例如:#define bwMCDR2_ADDRESS 4#define bsMCDR2_ADDRESS 17#define bmMCDR2_ADDRESS BIT_MASK(MCDR2_ADDRESS)#define BIT_MASK(_bf) (1U (bw # _bf) - 1) (bs # _bf)#define SET_BITS(_dst, _bf, _val) (_dst) = (_dst) & (BIT_MASK(_bf) | (_val) (

15、bs # _bf) & (BIT_MASK(_bf)SET_BITS(MCDR2, MCDR2_ADDRESS, RegisterNumber)函数和宏函数的区别就在于,宏函数占用了大量的空间,而函数占用了时间。 函数调用是要使用系统的栈来保存数据的,如果编译器里有栈检查选项,一般在函数的头会嵌入一些汇编 语句对当前栈进行检查;同时,CPU也要在函数调用时保存和恢复当前的现场,进行压栈和弹栈操作,所以,函数调用需要一些CPU时间。而宏函数不存在这个问题。宏函数仅仅作为预先写好的代码嵌入到当前程序,不会产生函数调用,所以仅仅是占用了空间,在频繁调用同一个宏函数的时候,该现象尤其突出。Data o

16、ptimization数据优化比算法优化层低一级的是数据优化层,我们可以通过改变算法使用的数据类型来优化算法。主要的目的是使处理的数据和目标结构的特性相一致。这项优化不需要大量的代码重写,并独立于算法优化的执行而执行.例如:确定浮点型变量和表达式是float 型为了让编译器产生更好的代码,必须确定浮点型变量和表达式是float 型的。要特别注意的是,以;F;或;f;为后缀(比如:2.718f)的浮点常量才是float 型,否则默认是double 型。为了避免float 型参数自动转化为 double ,请在函数声明时使用 float 。使用32位的数据类型编译器有很多种,但它们都包含的典型的

17、32位类型是:int , signed , signed int , unsigned , unsigned int long signed long long int signed long int unsigned long unsigned long int 。尽量使用32位的数据类型,因为它们比 16位的数据甚至8位的数据更有效率。明智使用有符号整型变量在很多情况下,你需要考虑整型变量是有符号还是无符号类型的。在许多地方,考虑是否使用有符号的变量是必要的。在一些情况下,有符号的运算比较快;但在一些情况下却相反。比如:整型到浮点转化时,使用大于16位的有符号整型比较快。因为 x86构架中

18、提供了从有符号整型转化到浮点型的指令, 但没有提供从无符号整型转化到浮点的指令。在整数运算中计算商和余数时,使用无符号类型比较快。Instruction flow optimization指令流优化第三层优化的目标是低级指令流。比较常见的技术是循环合并(loop merging),循环展开(unrolling), 软件流水(software pipelining) 。循环合并如果两个循环计数差不多、循环执行互不相同的操作, 可以把它们合并在一起组成一个循环。当两个循环的负荷都不满时,这是非常有用的。循环展开循环展开就是把循环计数小的循环展开,成为非循环形式的串行程序,或者把循环计数大的循环部分

19、展开,减少循环迭代次数, 这样可以节省了用于循环设置、初始化、增加和校对循 环计数器的时间。大多数编译器可以自动完成这项工作,手工编译会出现错例如:for( int i = 0; i 3 ; i+ ) arrayi = i ;逻辑上等同于:array0 = 0; array1 = 1, array2 = 2;软件流水软件流水是用来安排循环指令,使这个循环多次迭代并行执行的一种技术。在嵌套循环中,编译器仅对最里面的循环执行软件流水,因此对执行周期很少的内循环作循环展开,外循环进行软件流水,这样可以改进C代码并行执行的性能。 使用软件流水还应当注意: 尽管软件 流水循环可以包含内联函数,但是不能包

20、含函数调用;在循环中不可以有条件终止指令;在循环体中不可以修改循环控制变量。3、总结语现代的C和C+编译器都提供了一定程度上的代码优化。然而,大部分由编译器执行的优化 仅涉及执行速度和代码大小的一个平衡。你的程序能够变得更快或者更小,但是不可能又变快又变小。上面介绍的方法主要是为了提高代码的效率。但是事实上,在使用这些技术提高代码运行速度的同时会相应的产生一些负面的影响,比如增加代码的大小、降低程序可读性等。不过你可以让C/C+编译器来进行减少代码大小的优化,而手动利用编程来减少代码的执行时间。在嵌入式程序设计中合理地使用这几种技术有时会达到很好的优化效果。C语言高效编程的四大绝招编写高效简洁

21、的C语言代码,是许多软件工程师追求的目标。本文就是针对编程工作中的一些体会和经验 做相关的阐述。第一招:以空间换时间计算机程序中最大的矛盾是空间和时间的矛盾,那么,从这个角度出发逆向思维来考虑程序的效率问题,我们就有了解决问题的第1招-以空间换时间。比如说字符串的赋值:方法A:通常的办法#define LEN 32char stringl LEN;memset (string1,0,LEN);strcpy (string1,This is a example!);方法B:const char string2LEN =This is a example!; char * cp;cp = stri

22、ng2 ;使用的时候可以直接用指针来操作。从上面的例子可以看出,A和B的效率是不能比的。在同样的存储空间下,B直接使用指针就可以操作了,而A需要调用两个字符函数才能完成。B的缺点在于灵活性没有 A好。在需要频繁更改一个字符串内容的时候,A具有更好的灵活性;如果采用方法 B,则需要预存许多字符串,虽然占用了大量的内存,但是 获得了程序执行的高效率。如果系统的实时性要求很高,内存还有一些,那我推荐你使用该招数。该招数的变招 -使用宏函数而 不是函数。举例如下:方法C:#define bwMCDR2_ADDRESS 4#define bsMCDR2_ADDRESS 17 int BIT_MASK(i

23、nt _bf) (return (1U (bw # _bf) - 1) (bs # _bf);)void SET_BITS(int _dst, int _bf, int _val) (_dst = (_dst) & (BIT_MASK(_bf) |(_val) (bs # _bf)& (BIT_MASK(_bf)SET_BITS(MCDR2, MCDR2_ADDRESS,RegisterNumber);方法D:#define bwMCDR2_ADDRESS 4#define bsMCDR2_ADDRESS 17#define bmMCDR2_ADDRESS BIT_MASK(MCDR2_ADD

24、RESS)#define BIT_MASK(_bf)(1U (bw # _bf) - 1) (bs # _bf)#define SET_BITS(_dst, _bf, _val)(_dst) = (_dst) & (BIT_MASK(_bf)| (_val) (bs # _bf)& (BIT_MASK(_bf)SET_BITS(MCDR2, MCDR2_ADDRESS,RegisterNumber);函数和宏函数的区别就在于,宏函数占用了大量的空间,而函数占用了时间。大家要知道的是,函数 调用是要使用系统的栈来保存数据的,如果编译器里有栈检查选项,一般在函数的头会嵌入一些汇编语句 对当前栈进行

25、检查;同时,CPU&要在函数调用时保存和恢复当前的现场,进行压栈和弹栈操作,所以,函数调用需要一些CPU时间。而宏函数不存在这个问题。宏函数仅仅作为预先写好的代码嵌入到当前程序,不会产生函数调用,所 以仅仅是占用了空间,在频繁调用同一个宏函数的时候,该现象尤其突出。D方法是我看到的最好的置位操作函数,是ARM公司源码的一部分,在短短的三行内实现了很多功能,几乎涵盖了所有的位操作功能。C方法是其变体,其中滋味还需大家仔细体会。第二招:数学方法解决问题现在我们演绎高效 C语言编写的第二招-采用数学方法来解决问题。数学是计算机之母,没有数学的 依据和基础,就没有计算机的发展,所以在编写程序的时候,采

26、用一些数学方法会对程序的执行效率有数 量级的提高。举例如下,求 1100的和。方法E:int I , j;for (I = 1 ;I3;J = 456 - (456 4 4);在字面上好像H比G麻烦了好多,但是,仔细查看产生的汇编代码就会明白,方法G调用了基本的取模函数和除法函数,既有函数调用,还有很多汇编代码和寄存器参与运算;而方法H则仅仅是几句相关的汇编,代码更简洁,效率更高。当然,由于编译器的不同,可能效率的差距不大,但是,以我目前遇到的 MS C ,ARM C来看,效率的差距还是不小。相关汇编代码就不在这里列举了。运用这招需要注意的是,因为 CPU的不同而产生的问题。比如说,在PC上用

27、这招编写的程序,并在PC上调试通过,在移植到一个 16位机平台上的时候,可能会产生代码隐患。所以只有在一定技术进阶的 基础下才可以使用这招。第四招:汇编嵌入高效C语言编程的必杀技,第四招一一嵌入汇编。在熟悉汇编语言的人眼里, C语言编写的程序都是垃圾。这种说法虽然偏激了一些,但是却有它的道理。汇编语言是效率最高的计算机语言,但是,不可能 靠着它来写一个操作系统吧?所以,为了获得程序的高效率,我们只好采用变通的方法-嵌入汇编,混合编程。举例如下,将数组一赋值给数组二,要求每一字节都相符。char string11024,string21024;方法Iint I;for (I =0 ;I1024;

28、I+)*(string2 + I) = *(string1 + I)方法J#ifdef _PC_int I;for (I =0 ;I1024;I+)*(string2 + I) = *(string1 + I);#else#ifdef _ARM_asmMOV R0,string1MOV R1,string2MOV R2,#0loop:LDMIA R0!, R3-R11STMIA R1!, R3-R11ADD R2,R2,#8CMP R2, #400BNE loop#endif方法I是最常见的方法,使用了 1024次循环;方法J则根据平台不同做了区分,在 ARM平台下,用嵌 入汇编仅用128次循环就完成了同样的操作。 这里有朋友会说,为什么不用标准的内存拷贝函数呢 ?这是因 为在源数据里可能含有数据为 0的字节,这样的话,标准库函数会提前结束而不会完成我们要求的操作。 这个例程典型应用于 LCD数据的拷贝过程。根据不同的 CPU熟练使用相应的嵌入汇编,可以大大提高程 序执行的效率。虽然是必杀技,但是如果轻易使用会付出惨重的代价。这是因为,使用了嵌入汇编,便限制了程序的 可移植性,使程序在不同平台移植的过程中,卧虎藏龙,险象环生!同时该招数也与现代软件工程的思想 相违背,只有在迫不得已的情况下才可以采用。

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

当前位置:首页 > 社会民生


经营许可证编号:宁ICP备18001539号-1