数字服务商“冰鲸科技”完成千万融资,云时光资本
06-17
本文将介绍如何优化ARM平台的C代码,从数据类型选择、数据结构组织、局部变量选择、函数内联内联、编译器选项、循环扩展、条件从执行、数据操作转换、内存优化、代码大小优化等角度给出了常用的优化方法。
C数据类型 C语言程序优化与编译器和硬件系统都有关系。
设置某些编译器选项是最直接、最简单的优化方法。
默认情况下,armcc 启用所有优化功能,而 GNU 编译器的优化默认关闭。
ARM C编译器中定义的char类型是8位无符号的,这与流行编译器的默认char类型不同,后者是8位有符号的。
因此,当循环中使用char变量和条件i≥ 0时,就会出现无限循环。
为此,您可以使用 fsigned - char(对于 gcc)或 -zc(对于 armcc)将 char 更改为有符号。
其他变量类型如下: char 无符号 8 位字节数据 short 有符号 16 位半字节数据 int 有符号 32 位字数据 long 有符号 32 位字数据 long long 有符号 64 位双字数据 局部变量尽可能使用 32 位数据类型 ARM 指令集支持有符号/无符号 8 位、16 位和 32 位位整数和浮点变量。
正确使用变量类型不仅可以节省代码,还可以提高代码运行效率。
应尽可能避免使用char和short 类型的ARM局部变量,因为操作8位/16位局部变量往往比操作32位变量需要更多的指令。
大多数 ARM 数据处理操作都是 32 位的。
局部变量应尽可能使用32位数据类型(int或long)。
即使处理8位或16位值,也应避免使用char和short。
边界对齐,除非char或short的数据归零(如=0,多用于取模运算)。
否则,编译器将添加代码来处理大于 Short 和 char 值范围的情况。
另外,在处理表达式时,还必须谨慎选择数据类型。
请比较以下 3 函数及其汇编代码。
Int wordinc(inta) wordinc { ADD a1,a1,#1 return a + 1; MOV pc,lr } 短路 短shortinc(shorta) ADD a1,a1,#1 { MOV a1,a1,LSL #16 return a + 1; MOV a1,a1,ASR #16ARM } MOV pc,lr Char charinc(chara) charinc { ADD a1,a1,#1 返回 a + 1; AND a1,a1,#&ff } MOV pc,lr 可以看出,操作 3 2 位变量比操作 8 位和 16 位变量需要更少的指令。
另外,对于加载16位数据 如果使用LDRH指令,则无法使用桶形移位器,所以只能先进行偏移操作,然后寻址(可以使用指针增量寻址,而不使用数组)在下表中,增量寻址a=data[i++]不如a=*(data++)),同样会导致性能不佳。
但使用指针代替数据操作可以避免这个问题。
声明全局变量时,需要考虑最优的内存布局,使得各类变量能够在32位空间的基础上对齐,从而减少不必要的存储空间浪费,提高运行效率。
关于函数参数类型 函数参数和返回值应尽量使用int类型。
ARM中函数的前四个整数参数通过寄存器r0、r1、r2和r3传递,后续的整数参数通过堆栈传递。
因此,尽量限制函数参数不超过四个,也可以将相关参数组织在一个结构体中传递。
对于比较小的被调用函数和调用函数,可以放在同一个源文件中并限制为静态调用,编译器可以对其进行优化。
使用_inline内联对性能影响较大的重要函数,可以有效减少函数调用的额外开销。
对于编译器来说,armcc符合ATPCS的要求。
第一到第四个参数依次通过r0~r4传递,其他参数通过堆栈传递,返回值通过r0传递。
因此,为了在寄存器中完成大部分操作,参数最好不要超过4个。
另外,可用的通用寄存器有12个,所以尽量将局部变量控制在12个以内,效率会提高。
同时,由于编译器比较保守,指针别名会造成冗余的读操作,所以尽量少用。
循环优化部分 循环是编程中非常常见的结构。
在嵌入式系统中,微处理器很大一部分执行时间是在循环中运行的,因此非常有必要关注循环的执行效率。
除了在保证系统正常工作的同时尽可能简化核心循环体的流程之外,正确高效的循环结束标志条件也非常重要。
* 使用递减至零的循环体来节省指令和寄存器的使用; * 使用无符号循环计数值,并以条件 i != 0 终止,这样编译器就可以使用一条 BNE(不为零则跳转)指令替代 CMP(比较)和 BLE 两条指令 (小于则跳转),不仅减少了代码大小,还加快了ARM的运行速度; * 如果循环体至少执行一次,请优先选择 do-while,这样编译器就不会生成额外的代码来处理循环次数为 0 的情况; * 如果需要,展开循环体;虽然会增加循环的代码量,但是会减少循环跳转的开销; * 尽量使用数组大小??为4或8的规范,并利用这个倍数来扩展循环体寄存器分配; * 尽量限制函数内循环使用的局部变量数量不超过12个,这样编译器就可以将变量分配到寄存器; * 可以引导编译器通过检查变量是否属于最内层循环来判断变量的重要性; * 使用局部变量保存公共子表达式的值,确保表达式只计算一次; * 避免使用局部变量的地址,否则访问该变量的效率会较低; 结构的处理 小元素放在结构的开头,大元素放在结构的末尾;避免使用过大的结构,而代之以分层的小结构;手动向 API 结构添加填充位以提高可移植性; 项 请谨慎使用引发类型,因为其大小取决于编译器。
对于位域,尽量使用define或者enum来代替位域;使用逻辑运算对边界未对齐的数据和字节排列进行位域运算;尽量避免使用边界未对齐的数据; use char * 指向任何字节对齐的数据,并且可以与逻辑运算一起使用以访问任何边界和排列的数据。
数据运算的处理 除法和余数 ARM指令集不提供整数除法。
除法是通过C语言函数库(有符号_rt_sdiv和无符号_rt_udiv)中的代码实现的。
一次 32 位除法需要 20~ 个周期,具体取决于分子和分母的值。
除法运算所用的时间是时间常数乘以每一位除法所需的时间: 时间(分子/分母)=C0+C1×log2(分子/分母) =C0+C1× (log2(分子)-log2(分母)) 因为除法执行周期长,消耗大量资源,所以在编程中应避免除法。
以下是一些避免调用 Division 的解决方法: 在一些具体的编程中,除法可以改写为乘法。
例如:(x/y)>z,当已知y为正数且y×z为整数时,可写为x>(z×y)。
尽可能使用2的幂作为除数。
编译器使用移位操作来完成除法,比如比比较合适。
在编程中,无符号除法比有符号除法更快。
使用余数运算的目的之一是进行模计算。
此类操作有时可以使用if判断语句来完成。
考虑以下应用: Uint counter2(uint count) { if(++count>=) count=0; return(count); } 对于某些特殊的除法和余数运算,查表法也能取得良好的运算效果。
除以某些常量时,编写特定的函数来完成此操作将比编译生成的代码高效得多。
ARM的C语言库中有两个这样的将有符号数和无符号数除以10的函数,用于完成对十进制数的快速运算。
在toolkit子目录下的examples\explasm\div.c和examples\thumb\div.c文件中,有这两个函数的ARM版本和Thumb版本。
其他运算 使用左移/右移运算,而不是乘除2。
运算:通常,ARM乘法或2次方除法可以通过左移或右移n 位来完成结束。
事实上,当乘以任何整数时,您可以使用移位和加法来代替乘法。

ARM 7中的加法和移位可以用一条指令完成,并且执行时间比乘法指令少。
例如: i = i *5 可以使用关系 i = (i=0, x==0, x!=0; 对于无符号变量,x==0, x!=0( 或 x >0) 关系运算符。
对于编程中的条件语句,应尽可能简化if和else判断条件,它是面向ARM的。
,关系表达式中相似的条件应该组合在一起,以便编译器优化判断条件,由于ARM指令可以有条件地执行,因此充分利用cpsr指令将使程序更加高效。
所有指令都可以包含一个可选的条件代码,当程序状态寄存器(PSR)中的条件代码标志满足指定条件时,通常可以省略对ARM指令的单独判断,从而减少代码。
规模和提高程序效率 管道优化 每个ARM处理器都有自己的流水线结构,参考ARM核心流水线——ARM7、ARM9E、ARM11、Cortex-A系列处理器(/zixunimg/eepwimg/houh-.blog..com/blog/static//)。
管道延迟或阻塞会影响处理器的性能,因此管道应尽可能保持顺畅。
流水线延迟是不可避免的, ,但是延迟周期可以用于其他 ARM 操作。
LOAD/STORE 指令中的自动索引功能旨在利用 ARM 流水线延迟周期。
当流水线处于延迟周期时,处理器的执行单元被占用,但算术逻辑单元ARM(ALU)和桶形移位器可能空闲。
这时就可以利用它们来完成基地址寄存器的加法。
供后续指令使用的偏移操作。
例如:指令LDR R1,[R2],#4完成R1=*R2和R2+=4这两个操作,这是后索引的一个例子;以及指令 LDR R1, [R2, #4]!完成两个运算R1 = *(R2 + 4)和R2 +=4 ,这是预索引的一个例子。
管道阻塞的情况可以通过循环扩展以及添加其他操作来改善。
可以展开循环,减少循环指令中跳转指令的比例,从而提高代码效率。
下面用一个内存复制函数来说明ARM。
void memcopy(char *to, char *from, unsigned int nbytes) { while(nbytes--)ARM *to++ = *from++; } 很简单为了方便起见,这里假设nbytes是16的ARM倍数(余数的处理省略)。
上面的函数每处理一个字节都要进行一次判断并跳转。
循环体可以如下扩展: void memcopy(char *to, char *from, unsigned int nbytes) { while(nbytes) { *to++ = *from++ ; *to++ = *from++; *to++ = *from++; *to++ = *from++; n字节 - = 4; } } 结果,循环体中的指令数量增加,但循环数量减少。
ARM跳转指令的负面影响已经减弱。
利用ARM 7处理器的32位字长特性, 上述代码还可以进一步调整为: void memcopy(char *to, char *from, unsigned int nbytes) { int * p_to = (int *)to; int *p_from = (int *)from; while(nbytes) { *p_to++ = *p_from++; *p_to++ = *p_from++; *p_to++ = *p_from++; *p_to++ = *p_from++; nbytes - = 16; } } 优化后,一个循环可以处理16个字节。
跳转指令对ARM的影响进一步减弱。
不过可以看出,调整后的代码,代码量有所增加。
内存相关的优化方法 其他与存储相关的操作可以加快程序执行速度,比如使用查表代替计算。
当处理器资源紧张而内存资源相对丰富时, 可以牺牲存储空间来换取运行速度。
例如,当需要频繁计算正弦或余弦函数值时,可以预先计算出函数值并将其放置在内存中以供后续ARM搜索。
充分利用片内ARM芯片中的高速RAM,即ARM芯片中的指令和数据TCM 或L1 RAM和L2 RAM。
处理器对片内RAM的访问速度比对外部RAM的访问速度要快,因此应尽可能将程序转移到片上RAM 。
如果程序太大,无法完全放入片内RAM,可以考虑ARM将最常用的数据或程序段转移到片内RAM ,以提高程序运行效率。
这就是Cache的概念,它还可以通过优化数据和代码的组织来提高数据和代码的访问效率。
代码大小优化 精简指令集计算机的一个重要特征是指令长度是固定的。
这样可以简化指令解码过程,但很容易导致代码大小的增加。
为了避免这个问题,可以考虑采取以下措施来减少程序ARM代码量。
1)、使用多寄存器操作指令 ARM指令集中的多寄存器操作指令LDM/STM可以加载/存储多个寄存器,这是保存的/ 当恢复寄存器组的状态和复制大块数据时,它非常有效。
例如,将寄存器R4~R12和R14的内容保存到堆栈中,总共需要10条STR指令,以及1条STMEA R13!, {R4 ?? R12,R14}指令也可以达到同样的目的。
节省的指令存储空间是相当可观的。
不过需要注意的是,虽然一条LDM/STM 指令可以替代多条LDR/STR指令,但这并不意味着程序运行速度得到了ARM的提升。
事实上,处理器在执行LDM/STM 指令时,仍然将其拆分成多个单独的LDR/STR指令来执行。
2)、合理安排变量的顺序 ARM 7处理器要求ARM程序中的32位/16位变量必须是字/半字对齐的,也就是说如果变量的顺序不对齐合理排列, 可能会造成存储空间的浪费。
例如:一个结构体中有四个32位int类型变量i1~i4和四个8位char类型变量c1~ c4,如果根据i1,c1,i2,c2,i3,c3,i4,c4时顺序交错存储时,由于整型变量的对齐方式,位于两个整型变量中间的8位char 变量实际上占用了32位内存,导致存储空间的浪费。
为了避免这种情况,int类型变量和char 类型变量应该按照类似i1,i2,i3,i4,c1,c2,c3,c4的顺序连续存储。
3)、使用Thumb指令 为了从根本上减少ARM的代码大小,ARM开发了16位Thumb指令集。
Thumb 是 ARM 架构的扩展。
Thumb 指令集是最常用的 32 位 ARM 指令压缩为 16 位宽指令的集合。
在执行时,16 位指令会透明地实时解压缩为 32 位 ARM 指令,不会造成任何性能损失。
而且,程序在Thumb状态和ARM状态之间切换的开销为零。
与等效的 32 位 ARM 代码相比,Thumb 代码可节省高达 35% 的内存空间。
以上就是今天分享的如何针对嵌入式平台优化C代码。
版权声明:本文内容由互联网用户自发贡献,本站不拥有所有权,不承担相关法律责任。如果发现本站有涉嫌抄袭的内容,欢迎发送邮件 举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。
标签:
相关文章
06-17
06-06
06-18
06-06
06-18
06-18
06-18
最新文章
使用电子管有哪些注意事项?如何检查电子管之间是否短路?
博通支付1200万美元和解SEC财务欺诈指控
八名运营商高管确认加入虚拟运营商
内蒙古农牧区雷电灾害成因分析及防雷对策
北京联通将5G应用于世园会远程医疗急救
TD-SCDMA最后一轮冲刺测试启动,产业前景更加光明
专访阿里云总裁王健:云计算服务平台梦想成真
USB2.0控制器CY7C68013的接口设计与实现