红联Linux门户
Linux帮助

转一个【关于ARM的C语言优化】

发布时间:2009-11-14 14:55:06来源:红联作者:sh365
网上看到。具体网站比记得了。因为感觉不错就复制下来作为参考了。
感谢原作者。

-------------------------------------正文分割线------------------------------------
关于ARM的C语言优化
终于体会到优化程序有多么的痛苦,写代码简直就成了扫地雷,一不注意恐怕就炸出某个性能陷阱了。没办法,谁叫这就是所谓的资源受限。突然想起曾经在21IC 上看过的一编东西,苦劝大家千万千万别搞嵌入式,看来这的有点道理。无奈,一只脚已经进泥潭了,硬着头皮先挣扎一会吧。
今天看书的总结如下:
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[i] ); //这里表达式式整形的,所以返处理非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个之内,效率上会得到提升。同时,由于编译器比较保守,指针别名会引起多余的读操作,所以尽量少用。
文章评论

共有 1 条评论

  1. jive 于 2009-11-14 20:23:42发表:

    学习下,有深度