首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

ARM嵌入式开发中代码优化(1)

ARM嵌入式开发中代码优化(1)

关于嵌入开发中ARM的C代码优化
下面是网络上收集关于ARM的C代码优化方法,在嵌入开发中应该有用:
[声明:以下方法非本人发现和总结,均为有心之人无私贡献,谢谢他们的劳动与分享!]

=======================================================
C数据类型
1. C语言的程序优化与编译器和硬件系统都有关系,设置某些编译器选项是最直接最简单的优化方式。在默认的情况下,armcc是全部优化功能有效的,而GNU编译器的默认状态下优化都是关闭的。ARM C编译器中定义的char类型是8位无符号的,有别于一般流行的编译器默认的char是8位有符号的。所以循环中用char变量和条件 i ≥ 0时,就会出现死循环。为此,可以用fsigned - char(for gcc)或者-zc(for armcc)把char改成signed。  
其他的变量类型如下:      
char       无符号8位字节数据      
short       有符号16位半字节数据      
int       有符号32位字数据
long       有符号32位字数据      
long long   有符号64位双字数据
2. 关于局部变量  
大多数ARM数据处理操作都是32位的,局部变量应尽可能使用32位的数据类型(int或long)就算处理8位或者16位的数值,也应避免用char和short以求边界对齐,除非是利用char或者short的数据一出归零特性(如255+1=0,多用于模运算)。否则,编译器将要处理大于short和char取值范围的情况而添加代码。  
另外对于表达式的处理也要格外小心,如下例子:
short checksum_v3(short * data){  
unsigned int i;  
short sum = 0;  
for(i = 0; i < 64 ; i++){  
sum = (short)( sum + data );
//这里表达式式整形的,所以返处理非32位数据时,            
//要小心处理数据类型的转换。      
//原来short+short=int 但 int +int=int。。奇怪的处理  
}  
return sum;
}  
同时如上例的程序所示,这样在循环体中的每次运算都要进行类型转换,会降低程序的效率,可以先把其当作int来运算,然后再返回一个short类型。  
同时,由于处理的data[]是一个short型数组,用LDRH指令的话,不能使用桶型移位器,所以只能先进行偏移量的以为操作,然后再寻址,也会造成不佳的性能。解决的方法是用指针代替数组操作。如下:
short checksum_v4(short * data){
unsigned int i;  
int sum = 0;
for( i = ; i<64; i++)   {
sun += ( data ++);
}
return (short) sum;
}
3. 关于函数参数类型  
函数参数和返回值应尽量使用int类型。
另外,对于调用频率较低的全局变量,尽量使用小的数据类型以节省空间。


C循环结构
◎ 使用减数到零的循环体,以节省指令和寄存器的使用。
◎ 使用无符号的循环计数值,并用条件 i != 0中止。
◎ 如果循环体至少执行一次,用优先选用do-while。
◎ 适当情况下展开循环体。
◎ 尽量使用数组的大小是4或8的备述,用此倍数展开循环体 寄存器分配
◎ 尽量限制函数内部循环所用局部变量的数目,最多不超过12个,以便编译器能把变量分配到寄存器。
◎ 可以引导编译器,通过查看是否属于最内层循环的便赖宁嘎来去定某个变量的重要性。
函数调用  
ARM中的函数前4个整型参数通过寄存器r0、r1、r2、r3来传递,随后的整型参数通过堆栈来传递。(full desceding stack)。
◎ 尽量限制函数参数,不要超过四个,也可以把相关的参数组织在结构体传递。
◎ 把比较小的被调用函数和调用函数放在同一个源文件中,并且限定一,后调用,编译器能进行优化。
◎ 用_inline内联性能影响较大的重要函数。
指针别名
◎ 用一个局部变量来保存公共子表达式的值,保证该表达式只求一次值。
◎ 避免使用局部变量的地址,否则访问这个变量的效率较低。
结构体的安排
◎ 小的元素放在结构体的开始,大的元素放在结构体的最后
◎ 避免使用过大的结构体,用层次话的小结构体代替。
◎ 人工对API的结构体增加填充位以提高移植性。
◎ 枚举类型要慎用,因为它的大小与编译器相关。
位域
◎ 尽量用define或者enum来代替位域
◎ 用逻辑运算来丢位域操作 边界不对齐数据和字节排列方式
◎ 尽量避免使用边界不对齐数据;
◎ 用char× 可指向任意字节对齐的的数据,与逻辑运算配合,可访问任意边界和排列的数据。
除法
◎ 一堆算法,不好写,总的来说是以乘代除,配合移位运算。 内联函数和内嵌汇编
◎ 没什么好写的,就是内联减少调用开销,内嵌汇编提高运行效率。 总结  
总的来说,高级语言的优化和编译器、硬件结构有关。  
硬件上,ARM一般为32位总线,以32位访问数据的速度较快。局部变量和其他常用的变量要尽量利用32位的int类型,组织结构体时,也要注意元素的位置(小前大后),以节省空间。另外,由于ARM指令可条件执行,所以充分利用cpsr会使程序更有效率。同时注意好类型之间的运算,尽量减少转型操作。任何时候除法和取模运算可以同时取得结果而不会额外增加运算过程,但单单对于除法,还是以乘代除比较划算。  
对于编译器,armcc遵从ATPCS的要求,第一到第四个参数依次通过r0~r4传递,其他参数通过堆栈传递,返回值用r0传递,因此,为了把大部分操作放在寄存器中完成,参数最好不多与4个。另外,可用的通用寄存器有12个,所以尽量将局部变量控制在12个之内,效率上会得到提升。同时,由于编译器比较保守,指针别名会引起多余的读操作,所以尽量少用。=====================================================


• 数据类型
o 存放在寄存器中的局部变量(尤其是循环变量)应尽可能使用32位数据类型int(=long),8位变量不节省任何空间和时间;
o 即使传输一个8位数据,函数参数和返回值使用32位类令会更有效;
o 能用指针递增寻址就不用数组下表递增寻址a=data[i++]不如a=*(data++);
o 除法运算使用无符号数更快;
o 存放在存储器中的数组和全局变量,尽可能使用小尺寸数据类型;
o short型数组尽量避免使用数组基地址的偏移量,因为LDRH指令不支持偏移寻址;
o 存储器变量和寄存器变量相互赋值时使用显式类型转换,其他情况下避免不必要的类型转换;
• 循环结构
o 采用减计数循环比增计数循环更好,终止条件尽量写 i != 0 ;循环变量起始值是变量且不等于0的情况下用do-while循环更优(终止条件在后);
o 若循环体过于简单,比如少于4个周期,可展开循环体(重复写几遍循环体代码),以免循环体代码还不如循环本身执行周期长;
o 尽量限制函数内部循环所用局部变量的数据,最多不要超过12个,这样编译器就可以把他们都分配给ARM寄存器;

返回列表