C源程序优化
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
电子下载站 www.bbww.net 资料版权归合法所有者所有 严禁用于商业用途
C 源程序优化
对程序进行优化,通常是指优化程序代码或程序执行速度。优化代码和优化速度实际上是一 个予盾的统一,一般是优化了代码的尺寸,就会带来执行时间的增加,如果优化了程序的执 行速度,通常会带来代码增加的副作用,很难鱼与熊掌兼得,只能在设计时掌握一个平衡点。 1、程序的书写结构 虽然书写格式并不会影响生成的代码质量,但是在实际编写程序时还是应该尊循一定的书写 规则,一个书写清晰、明了的程序,有利于以后的维护。在书写程序时,特别是对于 While、 for、do…while、if…elst、switch…case 等语句或这些语句嵌套组合时,应采用“缩格”的书 写形式, 2、标识符 程序中使用的用户标识符除要遵循标识符的命名规则以外, 一般不要用代数符号(如 a、 b、 x1、 y1)作为变量名,应选取具有相关含义的英文单词(或缩写)或汉语拼音作为标识符,以增加程 序的可读性,如:count、number1、red、work 等。 3、程序结构 C 语言是一种高级程序设计语言,提供了十分完备的规范化流程控制结构。因此在采用 C 语 言设计单片机应用系统程序时,首先要注意尽可能采用结构化的程序设计方法,这样可使整 个应用系统程序结构清晰,便于调试和维护。于一个较大的应用程序,通常将整个程序按功 能分成若干个模块,不同模块完成不同的功能。各个模块可以分别编写,甚至还可以由不同 的程序员编写,一般单个模块完成的功能较为简单,设计和调试也相对容易一些。在 C 语言 中,一个函数就可以认为是一个模块。所谓程序模块化,不仅是要将整个程序划分成若干个 功能模块,更重要的是,还应该注意保持各个模块之间变量的相对独立性,即保持模块的独 立性,尽量少使用全局变量等。对于一些常用的功能模块,还可以封装为一个应用程序库, 以便需要时可以直接调用。但是在使用模块化时,如果将模块分成太细太小,又会导致程序 的执行效率变低(进入和退出一个函数时保护和恢复寄存器占用了一些时间)。 4、定义常数 在程序化设计过程中,对于经常使用的一些常数,如果将它直接写到程序中去,一旦常数的 数值发生变化,就必须逐个找出程序中所有的常数,并逐一进行修改,这样必然会降低程序 的可维护性。因此,应尽量当采用预处理命令方式来定义常数,而且还可以避免输入错误。 5、使用条件编译 能够使用条件编译(ifdef)的地方就使用条件编译而不使用 if 语句,有利于减少编译生成的代 码的长度。
严禁用于商业用途
数值大小不变,但是生成的代码却少了非常多。在很多情况下,如果忽略小数点部分对整个 数值的影响不大,就忽略小数点部分,改为整型或长整型。如果在中间变量为浮点型且不能 忽略小数点,也可以将其乘以 10n 方后转换为长整型数,但在最后运算时应记着除去 10n。 6、循环 (1)、循环语句 对于一些不需要循环变量参加运算的任务可以把它们放到循环外面, 这里的任务包括表达式、 函数的调用、指针运算、数组访问等。应该将没有必要执行多次的操作全部集合在一起,放 到一个 init 的初始化程序中进行。 (2)、延时函数: 通常使用的延时函数均采用自加的形式: void delay (void) {
5 欢迎光临中国最大的电子工程师应用网站 网址:www.bbww.net
电子下载站 www.bbww.net 资料版权归合法所有者所有
unsigned int i; for (i=0;i<1000;i++) ; } 将其改为自减延时函数: void delay (void) { unsigned int i; for (i=1000;i>0;i--) ; }
4 欢迎光临中国最大的电子工程师应用网站 网址:www.bbww.net
电子下载站 www.bbww.net 资料版权归合法所有者所有
a=a*9 可以改为: a=(a<<3)+a (4)、少用浮点运算 int a=200; float b; b=a*89.65 在上例中,如果能够不使用浮点运算,而改为长整型,如下: int a=200; long int b; b=a*8965/100;
严禁用于商业用途
两个函数的延时效果相似,但几乎所有的 C 编译对后一种函数生成的代码均比前一种代码少 1~3 个字节, 因为几乎所有的 MCU 均有为 0 转移的指令, 采用后一种方式能够生成这类指令。 在使用 while 循环时也一样,使用自减指令控制循环会比使用自加指令控制循环生成的代码 更少 1~3 个字母。 但是在循环中有通过循环变量“i”读写数组的指令时,使用预减循环时有可能使数组超界, 要引起注意。 (3)while 循环和 do…while 循环 用 while 循环时有以下两种循环形式: unsigned int i; i=0; while (i<1000) {
3 欢迎光临中国最大的电子工程师应用网站 网址:www.bbww.net
电子下载站 www.bbww.net 资料版权归合法所有者所有
代替。 (2)、平方运算 a=pow(a,2.0); 可以改为: a=a*a;
严禁用于商业用途
说明:在有内置硬件乘法器的单片机中(如 51 系列),乘法运算比求平方运算快得多,因为浮 点数的求平方是通过调用子程序来实现的,在自带硬件乘法器的 AVR 单片机中,如 ATMega163 中, 乘法运算只需 2 个时钟周期就可以完成。 就算是在没有内置硬件乘法器的 AVR 单片机中,乘法运算的子程序比平方运算的子程序代码短,执行速度快。 如果是求 3 次方,如: a=pow(a,3.0); 更改为: a=a*a*a; 则效率的改善更明显。 (3)、用移位实现乘除法运算 a=a*4; b=b/4; 可以改为: a=a<<2; b=b>>2; 说明: 通常如果需要乘以或除以 2n, 都可以用移位的方法代替。 在 ICCAVR 中, 如果乘以 2n, 都不是调用子程序而是直接生成左移 n 位的代码,但乘以非 2n 的整数或除以任何整数,均调 用乘除法子程序运算。 用移位的方法得到代码比调用乘除法子程序生成的代码效率高得多。实际上,只要是乘以或 除以一个整数,均可以用移位的方法得到结果,如:
6 欢迎光临中国最大的电子工程师应用网站 网址:www.bbww.net
Байду номын сангаас
电子下载站 www.bbww.net 资料版权归合法所有者所有
i++; //用户程序 } 或: unsigned int i; i=1000; do i--; //用户程序 while (i>0);
严禁用于商业用途
在这两种循环中,使用 do…while 循环编译后生成的代码的长度短于 while 循环。 7、查表 在程序中一般不进行非常复杂的运算,如浮点数的乘除及开方,以及一些复杂的数学模型的 插补运算,对这些即消耗时间又消耗资源的运算,应尽量使用查表的方式,并且将数据表置 于程序存储区。如果直接生成所需的表比较困难,也尽量在启动时先计算,然后在数据存储 器中生成所需的表,后以在程序运行直接查表就可以了,减少了程序执行过程中重复计算的 工作量。 8、其它 比如使用在线汇编以及将字符串和一些常量保存在程序存储器中,均能够优化生成的代码。
2 欢迎光临中国最大的电子工程师应用网站 网址:www.bbww.net
电子下载站 www.bbww.net 资料版权归合法所有者所有
严禁用于商业用途
一种合适的数据结构也很重要, 比如你在一堆随机存放的数中使用了大量的插入和删除指令, 那使用链表要快得多。 数组与指针具有十分密码的关系,一般来说,指针比较灵活简洁,而数组则比较直观,容易 理解。对于大部分的 C 编译器,使用指针比使用数组生成的代码更短,执行效率更高。但是 在 Keil 中则相反,使用数组比使用的指针生成的代码更短。 2、使用尽量小的数据类型 能够使用字符型(char)定义的变量,就不要使用整型(int)变量来定义;能够使用整型变量定义 的变量就不要用长整型(long int),特别是能不用浮点型(float)变量就不要使用浮点型变量,使 用浮点型变量会使程序代码增加很大。当然,在定义变量后不能超过变量的作用范围,如果 超过变量的范围赋值,C 编译器并不报错,但程序运行结果却错了,而且这样的错误很难发 现。 在 keil 中,应说明指针所指向的对象类型,少用通用型指针。 在 ICCAVR 中,可以在 Options 中设定使用 printf 参数,尽量使用基本型参数(%c、%d、%x、 %X、%u 和%s 格式说明符),少用长整型参数(%ld、%lu、%lx 和%lX 格式说明符),至于浮 点型的参数(%f)则尽量不要使用,其它 C 编译器也一样。在其它条件不变的情况下,使用%f 参数,会使生成的代码的数量增加很多,执行速度降低。 3、使用自加、自减指令 通常使用自加、 自减指令和复合赋值表达式(如 a-=1 及 a+=1 等)都能够生成高质量的程序代码, 编译器通常都能够生成 inc 和 dec 之类的指令,而使用 a=a+1 或 a=a-1 之类的指令,有很多 C 编译器都会生成二到三个字节的指令。在 AVR 单片适用的 ICCAVR、GCCAVR、IAR 等 C 编 译器中,以上几种书写方式生成的代码是一样的,都能够生成 inc 和 dec 之类高质量的代码。 4、减少运算的强度 可以使用运算量小但功能相同的表达式替换原来复杂的的表达式。如下: (1)、求余运算。 a=a%8; 可以改为: a=a&7; 说明:位操作只需一个指令周期即可完成,而大部分的 C 编译器的“%”运算均是调用子程 序来完成,代码长、执行速度慢。通常,只要求是求 2n 方的余数,均可使用位操作的方法来
欢迎光临中国最大的电子工程师应用网站 网址:www.bbww.net
电子下载站 www.bbww.net 资料版权归合法所有者所有
6、表达式
严禁用于商业用途
对于一个表达式中各种运算执行的优先顺序不太明确或容易混淆的地方,应当采用圆括号明 确指定它们的优先顺序。一个表达式通常不能写得太复杂,如果表达式太复杂,时间久了以 后,自己也不容易看得懂,不利于以后的维护。 7、函数 对于程序中的函数,在使用之前,应对函数的类型进行说明,对函数类型的说明必须保证它 与原来定义的函数类型一致,对于没有参数和没有返回值类型的函数应加上“void”说明。 如果果需要缩短代码的长度,可以将程序中一些公共的程序段定义为函数,在 Keil 中的高级 别优化就是这样的。如果需要缩短程序的执行时间,在程序调试结束后,将部分函数用宏定 义来代替。注意,应该在程序调试结束后再定义宏,因为大多数编译系统在宏展开之后才会 报错,这样会增加排错的难度。 8、尽量少用全局变量,多用局部变量。因为全局变量是放在数据存储器中,定义一个全局变 量,MCU 就少一个可以利用的数据存储器空间,如果定义了太多的全局变量,会导致编译器 无足够的内存可以分配。 而局部变量大多定位于 MCU 内部的寄存器中, 在绝大多数 MCU 中, 使用寄存器操作速度比数据存储器快,指令也更多更灵活,有利于生成质量更高的代码,而 且局部变量所的占用的寄存器和数据存储器在不同的模块中可以重复利用。 9、设定合适的编译程序选项 许多编译程序有几种不同的优化选项,在使用前应理解各优化选项的含义,然后选用最合适 的一种优化方式。通常情况下一旦选用最高级优化,编译程序会近乎病态地追求代码优化, 可能会影响程序的正确性,导致程序运行出错。因此应熟悉所使用的编译器,应知道哪些参 数在优化时会受到影响,哪些参数不会受到影响。 在 ICCAVR 中,有“Default”和“Enable Code Compression”两个优化选项。 在 CodeVisionAVR 中, “Tiny”和“small”两种内存模式。 在 IAR 中,共有 7 种不同的内存模式选项。 在 GCCAVR 中优化选项更多,一不小心更容易选到不恰当的选项。 5.1.2 代码的优化 1、选择合适的算法和数据结构 应该熟悉算法语言,知道各种算法的优缺点,具体资料请参见相应的参考资料,有很多计算 机书籍上都有介绍。将比较慢的顺序查找法用较快的二分查找或乱序查找法代替,插入排序 或冒泡排序法用快速排序、合并排序或根排序代替,都可以大大提高程序执行的效率。.选择
C 源程序优化
对程序进行优化,通常是指优化程序代码或程序执行速度。优化代码和优化速度实际上是一 个予盾的统一,一般是优化了代码的尺寸,就会带来执行时间的增加,如果优化了程序的执 行速度,通常会带来代码增加的副作用,很难鱼与熊掌兼得,只能在设计时掌握一个平衡点。 1、程序的书写结构 虽然书写格式并不会影响生成的代码质量,但是在实际编写程序时还是应该尊循一定的书写 规则,一个书写清晰、明了的程序,有利于以后的维护。在书写程序时,特别是对于 While、 for、do…while、if…elst、switch…case 等语句或这些语句嵌套组合时,应采用“缩格”的书 写形式, 2、标识符 程序中使用的用户标识符除要遵循标识符的命名规则以外, 一般不要用代数符号(如 a、 b、 x1、 y1)作为变量名,应选取具有相关含义的英文单词(或缩写)或汉语拼音作为标识符,以增加程 序的可读性,如:count、number1、red、work 等。 3、程序结构 C 语言是一种高级程序设计语言,提供了十分完备的规范化流程控制结构。因此在采用 C 语 言设计单片机应用系统程序时,首先要注意尽可能采用结构化的程序设计方法,这样可使整 个应用系统程序结构清晰,便于调试和维护。于一个较大的应用程序,通常将整个程序按功 能分成若干个模块,不同模块完成不同的功能。各个模块可以分别编写,甚至还可以由不同 的程序员编写,一般单个模块完成的功能较为简单,设计和调试也相对容易一些。在 C 语言 中,一个函数就可以认为是一个模块。所谓程序模块化,不仅是要将整个程序划分成若干个 功能模块,更重要的是,还应该注意保持各个模块之间变量的相对独立性,即保持模块的独 立性,尽量少使用全局变量等。对于一些常用的功能模块,还可以封装为一个应用程序库, 以便需要时可以直接调用。但是在使用模块化时,如果将模块分成太细太小,又会导致程序 的执行效率变低(进入和退出一个函数时保护和恢复寄存器占用了一些时间)。 4、定义常数 在程序化设计过程中,对于经常使用的一些常数,如果将它直接写到程序中去,一旦常数的 数值发生变化,就必须逐个找出程序中所有的常数,并逐一进行修改,这样必然会降低程序 的可维护性。因此,应尽量当采用预处理命令方式来定义常数,而且还可以避免输入错误。 5、使用条件编译 能够使用条件编译(ifdef)的地方就使用条件编译而不使用 if 语句,有利于减少编译生成的代 码的长度。
严禁用于商业用途
数值大小不变,但是生成的代码却少了非常多。在很多情况下,如果忽略小数点部分对整个 数值的影响不大,就忽略小数点部分,改为整型或长整型。如果在中间变量为浮点型且不能 忽略小数点,也可以将其乘以 10n 方后转换为长整型数,但在最后运算时应记着除去 10n。 6、循环 (1)、循环语句 对于一些不需要循环变量参加运算的任务可以把它们放到循环外面, 这里的任务包括表达式、 函数的调用、指针运算、数组访问等。应该将没有必要执行多次的操作全部集合在一起,放 到一个 init 的初始化程序中进行。 (2)、延时函数: 通常使用的延时函数均采用自加的形式: void delay (void) {
5 欢迎光临中国最大的电子工程师应用网站 网址:www.bbww.net
电子下载站 www.bbww.net 资料版权归合法所有者所有
unsigned int i; for (i=0;i<1000;i++) ; } 将其改为自减延时函数: void delay (void) { unsigned int i; for (i=1000;i>0;i--) ; }
4 欢迎光临中国最大的电子工程师应用网站 网址:www.bbww.net
电子下载站 www.bbww.net 资料版权归合法所有者所有
a=a*9 可以改为: a=(a<<3)+a (4)、少用浮点运算 int a=200; float b; b=a*89.65 在上例中,如果能够不使用浮点运算,而改为长整型,如下: int a=200; long int b; b=a*8965/100;
严禁用于商业用途
两个函数的延时效果相似,但几乎所有的 C 编译对后一种函数生成的代码均比前一种代码少 1~3 个字节, 因为几乎所有的 MCU 均有为 0 转移的指令, 采用后一种方式能够生成这类指令。 在使用 while 循环时也一样,使用自减指令控制循环会比使用自加指令控制循环生成的代码 更少 1~3 个字母。 但是在循环中有通过循环变量“i”读写数组的指令时,使用预减循环时有可能使数组超界, 要引起注意。 (3)while 循环和 do…while 循环 用 while 循环时有以下两种循环形式: unsigned int i; i=0; while (i<1000) {
3 欢迎光临中国最大的电子工程师应用网站 网址:www.bbww.net
电子下载站 www.bbww.net 资料版权归合法所有者所有
代替。 (2)、平方运算 a=pow(a,2.0); 可以改为: a=a*a;
严禁用于商业用途
说明:在有内置硬件乘法器的单片机中(如 51 系列),乘法运算比求平方运算快得多,因为浮 点数的求平方是通过调用子程序来实现的,在自带硬件乘法器的 AVR 单片机中,如 ATMega163 中, 乘法运算只需 2 个时钟周期就可以完成。 就算是在没有内置硬件乘法器的 AVR 单片机中,乘法运算的子程序比平方运算的子程序代码短,执行速度快。 如果是求 3 次方,如: a=pow(a,3.0); 更改为: a=a*a*a; 则效率的改善更明显。 (3)、用移位实现乘除法运算 a=a*4; b=b/4; 可以改为: a=a<<2; b=b>>2; 说明: 通常如果需要乘以或除以 2n, 都可以用移位的方法代替。 在 ICCAVR 中, 如果乘以 2n, 都不是调用子程序而是直接生成左移 n 位的代码,但乘以非 2n 的整数或除以任何整数,均调 用乘除法子程序运算。 用移位的方法得到代码比调用乘除法子程序生成的代码效率高得多。实际上,只要是乘以或 除以一个整数,均可以用移位的方法得到结果,如:
6 欢迎光临中国最大的电子工程师应用网站 网址:www.bbww.net
Байду номын сангаас
电子下载站 www.bbww.net 资料版权归合法所有者所有
i++; //用户程序 } 或: unsigned int i; i=1000; do i--; //用户程序 while (i>0);
严禁用于商业用途
在这两种循环中,使用 do…while 循环编译后生成的代码的长度短于 while 循环。 7、查表 在程序中一般不进行非常复杂的运算,如浮点数的乘除及开方,以及一些复杂的数学模型的 插补运算,对这些即消耗时间又消耗资源的运算,应尽量使用查表的方式,并且将数据表置 于程序存储区。如果直接生成所需的表比较困难,也尽量在启动时先计算,然后在数据存储 器中生成所需的表,后以在程序运行直接查表就可以了,减少了程序执行过程中重复计算的 工作量。 8、其它 比如使用在线汇编以及将字符串和一些常量保存在程序存储器中,均能够优化生成的代码。
2 欢迎光临中国最大的电子工程师应用网站 网址:www.bbww.net
电子下载站 www.bbww.net 资料版权归合法所有者所有
严禁用于商业用途
一种合适的数据结构也很重要, 比如你在一堆随机存放的数中使用了大量的插入和删除指令, 那使用链表要快得多。 数组与指针具有十分密码的关系,一般来说,指针比较灵活简洁,而数组则比较直观,容易 理解。对于大部分的 C 编译器,使用指针比使用数组生成的代码更短,执行效率更高。但是 在 Keil 中则相反,使用数组比使用的指针生成的代码更短。 2、使用尽量小的数据类型 能够使用字符型(char)定义的变量,就不要使用整型(int)变量来定义;能够使用整型变量定义 的变量就不要用长整型(long int),特别是能不用浮点型(float)变量就不要使用浮点型变量,使 用浮点型变量会使程序代码增加很大。当然,在定义变量后不能超过变量的作用范围,如果 超过变量的范围赋值,C 编译器并不报错,但程序运行结果却错了,而且这样的错误很难发 现。 在 keil 中,应说明指针所指向的对象类型,少用通用型指针。 在 ICCAVR 中,可以在 Options 中设定使用 printf 参数,尽量使用基本型参数(%c、%d、%x、 %X、%u 和%s 格式说明符),少用长整型参数(%ld、%lu、%lx 和%lX 格式说明符),至于浮 点型的参数(%f)则尽量不要使用,其它 C 编译器也一样。在其它条件不变的情况下,使用%f 参数,会使生成的代码的数量增加很多,执行速度降低。 3、使用自加、自减指令 通常使用自加、 自减指令和复合赋值表达式(如 a-=1 及 a+=1 等)都能够生成高质量的程序代码, 编译器通常都能够生成 inc 和 dec 之类的指令,而使用 a=a+1 或 a=a-1 之类的指令,有很多 C 编译器都会生成二到三个字节的指令。在 AVR 单片适用的 ICCAVR、GCCAVR、IAR 等 C 编 译器中,以上几种书写方式生成的代码是一样的,都能够生成 inc 和 dec 之类高质量的代码。 4、减少运算的强度 可以使用运算量小但功能相同的表达式替换原来复杂的的表达式。如下: (1)、求余运算。 a=a%8; 可以改为: a=a&7; 说明:位操作只需一个指令周期即可完成,而大部分的 C 编译器的“%”运算均是调用子程 序来完成,代码长、执行速度慢。通常,只要求是求 2n 方的余数,均可使用位操作的方法来
欢迎光临中国最大的电子工程师应用网站 网址:www.bbww.net
电子下载站 www.bbww.net 资料版权归合法所有者所有
6、表达式
严禁用于商业用途
对于一个表达式中各种运算执行的优先顺序不太明确或容易混淆的地方,应当采用圆括号明 确指定它们的优先顺序。一个表达式通常不能写得太复杂,如果表达式太复杂,时间久了以 后,自己也不容易看得懂,不利于以后的维护。 7、函数 对于程序中的函数,在使用之前,应对函数的类型进行说明,对函数类型的说明必须保证它 与原来定义的函数类型一致,对于没有参数和没有返回值类型的函数应加上“void”说明。 如果果需要缩短代码的长度,可以将程序中一些公共的程序段定义为函数,在 Keil 中的高级 别优化就是这样的。如果需要缩短程序的执行时间,在程序调试结束后,将部分函数用宏定 义来代替。注意,应该在程序调试结束后再定义宏,因为大多数编译系统在宏展开之后才会 报错,这样会增加排错的难度。 8、尽量少用全局变量,多用局部变量。因为全局变量是放在数据存储器中,定义一个全局变 量,MCU 就少一个可以利用的数据存储器空间,如果定义了太多的全局变量,会导致编译器 无足够的内存可以分配。 而局部变量大多定位于 MCU 内部的寄存器中, 在绝大多数 MCU 中, 使用寄存器操作速度比数据存储器快,指令也更多更灵活,有利于生成质量更高的代码,而 且局部变量所的占用的寄存器和数据存储器在不同的模块中可以重复利用。 9、设定合适的编译程序选项 许多编译程序有几种不同的优化选项,在使用前应理解各优化选项的含义,然后选用最合适 的一种优化方式。通常情况下一旦选用最高级优化,编译程序会近乎病态地追求代码优化, 可能会影响程序的正确性,导致程序运行出错。因此应熟悉所使用的编译器,应知道哪些参 数在优化时会受到影响,哪些参数不会受到影响。 在 ICCAVR 中,有“Default”和“Enable Code Compression”两个优化选项。 在 CodeVisionAVR 中, “Tiny”和“small”两种内存模式。 在 IAR 中,共有 7 种不同的内存模式选项。 在 GCCAVR 中优化选项更多,一不小心更容易选到不恰当的选项。 5.1.2 代码的优化 1、选择合适的算法和数据结构 应该熟悉算法语言,知道各种算法的优缺点,具体资料请参见相应的参考资料,有很多计算 机书籍上都有介绍。将比较慢的顺序查找法用较快的二分查找或乱序查找法代替,插入排序 或冒泡排序法用快速排序、合并排序或根排序代替,都可以大大提高程序执行的效率。.选择