嵌入式平台ARM的C代码优化方法

发布者:未来架构师最新更新时间:2016-07-14 来源: eefocus关键字:嵌入式平台  ARM  C代码  优化方法 手机看文章 扫描二维码
随时随地手机看文章
本文介绍了ARM平台的C代码优化方法,从数据类型选择、数据结构组织、局部变量选择、函数inline内联、编译器选项、循环展开、条件执行、数据操作的转化、存储器的优化、代码尺寸的优化等角度给出常用的优化方法。

C数据类型

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位双字数据

局部变量尽可能采用32位数据类型

ARM 指令集支持有符号/ 无符号的8 位、16 位、32位整型及浮点型变量。恰当的使用变量的类型,不仅可以节省代码,并且可以提高代码运行效率。应该尽可能地避免使用char、short 型的ARM局部变量,因为操作8 位/16 位局部变量往往比操作3 2 位变量需要更多指令。 大多数ARM数据处理操作都是32位的,局部变量应尽可能使用32位的数据类型(int或long)就算处理8位或者16位的数值,也应避免用char和short以求边界对齐,除非是利用char或者short的数据一出归零特性(如255+1=0,多用于模运算)。否则,编译器将要处理大于short和char取值范围的情况而添加代码。另外对于表达式的处理也要格外小心选择数据类型,请对比下列3 个函数和它们的汇编代码。

Int wordinc(inta) wordinc

{ ADD a1,a1,#1

return a + 1; MOV pc,lr

}

shortinc

short 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

return a + 1; AND a1,a1,#&ff

} MOV pc,lr

可以看出, 操作3 2 位变量所需的指令要少于操作8位及16 位变量。另外对于16-bit数据的加载 用LDRH指令的话,不能使用桶型移位器,所以只能先进行偏移量的以为操作,然后再寻址(能用指针递增寻址就不用数组下表递增寻址a=data[i++]不如a=*(data++)),也会造成不佳的性能。但是用指针代替数据操作就可以规避这个问题。在全局变量声明时,需要考虑最佳的存储器布局,使得各种类型的变量能以32位的空间位基准对齐,从而减少不必要的存储空间浪费,提高运行效率。

关于函数参数类型

函数参数和返回值应尽量使用int类型。ARM中的函数前4个整型参数通过寄存器r0、r1、r2、r3来传递,随后的整型参数通过堆栈来传递。因而尽量限制函数参数,不要超过四个,也可以把相关的参数组织在结构体传递。 对于比较小的被调用函数和调用函数可以放在同一个源文件中,并且限定为static调用,编译器能进行优化。用_inline内联性能影响较大的重要函数可以有效减少函数调用的额外开销。对于编译器,armcc遵从ATPCS的要求,第一到第四个参数依次通过r0~r4传递,其他参数通过堆栈传递,返回值用r0传递,因此,为了把大部分操作放在寄存器中完成,参数最好不多与4个。另外,可用的通用寄存器有12个,所以尽量将局部变量控制在12个之内,效率上会得到提升。同时,由于编译器比较保守,指针别名会引起多余的读操作,所以尽量少用。

循环优化部分

循环是程序设计中非常普遍的结构。在嵌入式系统中,微处理器执行时间在循环中运行的比例较大,因此关注循环的执行效率是非常必要的。除了在保证系统正确工作的前提下尽量简化核循环体的过程以外,正确和高效的循环结束标志条件也非常重要。

* 使用减数到零的循环体,以节省指令和寄存器的使用;

* 使用无符号的循环计数值,并用条件 i != 0中止,这样编译器可以用一条BNE (若非零则跳转)指令代替CMP (比较)和BLE (若小于则跳转)两条指令,既减小代码尺寸,又加快了运行ARM速度;

* 如果循环体至少执行一次,用优先选用do-while,这样编译器不会产生额外的代码来处理循环次数为0的情况;

* 适当情况下展开循环体;虽然会增加循环的代码大小,但是会减少循环跳转的开销;

* 尽量使用数组的大小是4或8的备述,用此倍数展开循环体 寄存器分配;

* 尽量限制函数内部循环所用局部变量的数目,最多不超过12个,以便编译器能把变量分配到寄存器;

* 可以引导编译器,通过查看是否属于最内层循环的便赖宁嘎来去定某个变量的重要性;

* 用一个局部变量来保存公共子表达式的值,保证该表达式只求一次值;

* 避免使用局部变量的地址,否则访问这个变量的效率较低;

结构体的处理

小的元素放在结构体的开始,大的元素放在结构体的最后; 避免使用过大的结构体,用层次化的小结构体代替; 人工对API的结构体增加填充位以提高移植性; 枚举类型要慎用,因为它的大小与编译器相关。

对于位域, 尽量用define或者enum来代替位域;用逻辑运算来丢位域操作 边界不对齐数据和字节排列方式; 尽量避免使用边界不对齐数据; 用char * 可指向任意字节对齐的的数据,与逻辑运算配合,可访问任意边界和排列的数据。

数据运算的处理

除法和求余

ARM指令集中没有提供整数的除法,除法是由C语言函数库中的代码(符号型_rt_sdiv和无符号型的_rt_udiv)实现的。一个32位数的除法需要20~140个周期,依赖于分子和分母的取值。除法操作所用的时间是一个时间常量乘每一位除法所需要的时间:

Time(分子/分母)=C0+C1×log2(分子/分母)

=C0+C1×(log2(分子)-log2(分母))

由于除法的执行周期长,耗费的资源多,程序设计中应当尽量避免使用除法。以下是一些避免调用除法的变通办法:

  • 在某些特定的程序设计时,可以把除法改写为乘法。例如:(x/y)>z,在已知y是正数而且y×z是整数的情况下,就可以写为x>(z×y)。
  • 尽可能使用2的次方作为除数,编译器使用移位操作完成除法,如128就比100更加适合。在程序设计中,使用无符号型的除法要快于符号型的除法。
  • 使用求余运算的一个目的是为了按模计算,这样的操作有时可以使用if的判断语句来完成,考虑如下的应用:

Uint counter2(uint count)

{

if(++count>=100) 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<<2) + i 来代替。利用乘法代替乘方运算:ARM7 核中内建有32 ×8 ARM乘法器, 因此可以通过乘法运算来代替乘方运算以节约乘方函数调用的开销。例如: i = pow(i, 3.0) 可用 i = i*i*i 来代替。利用与运算代替求余运算:有时可以通过用与(AND )指令代替求余操作(% )来提高效率。例如:i = i % 8 可以用 i = i & 0x07 来代替。

条件执行

条件执行是程序中必不可少的基本操作。典型的条件执行代码序列是由一个比较指令开始的,接下来是一系列相关的执行语句。ARM中的条件执行是通过对运算结果标志位进行判断实现的,一些带标志位的运算结果中,N和Z标志位的结果与比较语句的结果相同。尽管在C语言中没有带标志位的指令,但在面向ARM的C语言程序中,如果运算结果是与0作比较,编译器会移去比较指令,通过一条带标志位指令实现运算和判断。因此,面向ARM的C语言程序设计的条件判断应当尽量采用"与0比较"的形式。C语言中,条件执行语句大多数应用在if条件判断中,也有应用在复杂的关系运算(<,==,>等)及位操运算(&&,!,and等)中的。面向ARM的C语言程序设计中,有符号型变量应尽量采取x<0、x>=0、x==0、x!=0的关系运算;对于无符号型的变量应采用x==0、x!=0(或者x>0)关系运算符。编译器都可以对条件执行进行优化。对于程序设计中的条件语句,应尽量简化if和else判断条件。与传统的C语言程序设计有所不同,面向ARM的C语言程序设计中,关系表述中类似的条件应该集中在一起,使编译器能够对判断条件进行优化。由于ARM指令可条件执行,所以充分利用cpsr会使程序更有效率。ARM 指令集的一个重要特征就是所有的指令均可包含一个可选的条件码。当程序状态寄存器(PSR )中的条件码标志满足指定条件时, 带条件码的指令才能执行。利用条件执行通常可以省去单独的判断ARM指令,因而可以减小代码尺寸并提高程序效率。

流水线优化

ARM处理器每种处理器都有自己的流水线结构,参考ARM核流水线——ARM7,ARM9E,ARM11,Cortex-A系列处理器(http://houh-1984.blog.163.com/blog/static/311278342011111083852771/ ).流水线延迟或阻断会对处理器的性能造成影响,因此应该尽量保持流水线畅通。流水线延迟难以避免, 但可以利用延迟周期进行其它ARM操作。 LOAD/STORE 指令中的自动索引(auto-indexing)功能就是为利用ARM流水线延迟周期而设计的。当流水线处于延迟周期时, 处理器的执行单元被占用, 算术逻辑单元ARM(ALU )和桶形移位器却可能处于空闲状态,此时可以利用它们来完成往基址寄存器上加一个偏移量的操作,供后面的指令使用。例如:指令 LDR R1, [R2], #4 完成 R1= *R2 及 R2 += 4 两个操作,是后索引(post-indexing)的例子;而指令 LDR R1, [R2, #4]! 完成 R1 = *(R2 + 4) 和 R2 +=4 两个操作,是前索引(pre-indexing)的例子。流水线阻断的情况可通过循环展开,加入其它的操作等方法加以改善。一个循环可以考虑展开unroll以减小跳转指令在循环指令中所占的比重, 进而提高代码效率。下面以一个内存复制函数加以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++;

nbytes - = 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 的内容保存到堆栈中,若用STR 指令共需要10 条,而一条STMEA R13!, {R4 ?? R12, R14} 指令就能达到相同的目的,节省的指令存储空间相当可观。不过需要注意的是, 虽然一条LDM/STM 指令能代替多条LDR/STR 指令,但这并不意味着程序运行速度得到了ARM提高。实际上处理器在执行LDM/STM 指令的时候还是将它拆分成多条单独的LDR/STR 指令来执行。

2)、 合理安排变量顺序

ARM 7 处理器要求ARM程序中的32 位/16 位变量必须按字/ 半字对齐,这意味着如果变量顺序安排不合理, 有可能会造成存储空间的浪费。例如:一个结构体中的4个32 位int 型变量i1 ~ i4 和4 个8 位char 型变量c1 ~ c4,若按照i1、c1、i2、c2、i3、c3、i4、c4 的顺序交错存放时, 由于整型变量的对齐会导致位于2 个整型变量中间的那个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% 以上。

关键字:嵌入式平台  ARM  C代码  优化方法 引用地址:嵌入式平台ARM的C代码优化方法

上一篇:在Cortex-A8平台下memcpy ARM/NEON汇编性能的测试
下一篇:GCC支持的ARM NEON 内联SIMD Intrinsics

推荐阅读最新更新时间:2024-03-16 15:00

Arm表示,如果与Nvidia的交易失败,公司将会停滞不前
本文编译自EETimes Arm日前表示,如果 Nvidia 的收购协议失败,Arm 作为一家独立公司的增长将面临重大障碍。 这份长达 29 页的文件详细介绍了Arm-Nvidia 联合对英国政府的回应,去年 11 月政府决定将该交易提交英国竞争与市场管理局 (CMA) 进行进一步调查。回应强调,如果没有英伟达的投资,Arm 在数据中心市场的增长以及与英特尔公司和 X86 现有公司的竞争中将处于严重劣势。该文件还解释了为什么 Arm不能IPO,同时指出 Arm 面临来自新兴 RISC-V 竞争对手的激烈竞争。 文件称,随着软银的投资接近尾声,Arm 发现自己正处于十字路口,使其被英伟达收购成为“独一无二的、千载难逢的机
[半导体设计/制造]
Intel 10nm代工 LG两款ARM芯片现身
  苹果最新推出的A11芯片再次证明,只有自主把控芯片才能让设备发挥最大的价值。而除了苹果之外,三星,华为等也在研发自家的芯片,很多人对此可能并不陌生,但可能有些读者可能不曾了解的是,LG也拥有自家的芯片——NUCLUN,尽管最终以失败告终。下面就随网络通信小编一起来了解一下相关内容吧。     现据最新消息,LG似乎正在研发两款全新的处理器。   根据欧盟知识产权局的申请文件,LG提交了两份商标申请文件,分别是“LG KROMAX Processor" 和 "LG EPIK Processor”。LG称它们指的是“芯片 ,多处理器芯片”。        当然,目前还并没有充分的证据表明这两款芯片会运用于智能手机,它们也有
[网络通信]
服务器市场的变数,ARM仍是最大看点?
近年来智能手机成为处理器技术发展的主要驱动力,一段时间以来我发现Linley 处理器会议中最有趣的部分便是专门针对移动应用的会议。 上周举行的一场会议并不是针对移动市场,而是一场专注于网络和服务器市场的会议。不过,这两类市场都在“移动数据”的推动下迅速增长。 事实上,智能手机发展得如此之快,使得它们已经成为互联网访问的主要方式,而且这种发展趋势只会不断加速,相比之下,台式机和笔记本电脑变得比较小众。 Jag Bolaria(Linley)在主题演讲中介绍了网络和服务器这部分行业的概况。在整体市场份额上,英特尔继续保持领先地位,其次是飞思卡尔。令我意外的是,AMD提升了在这一市场的份额,因为供应商都在寻找可以降低成本和替
[单片机]
ARM的综合实验设计
一、实验目的 学习做一个复杂的应用程序的方法和程序结构,结合应用以前16 次实验所学到的知识,编写一个比较综合的应用。 二、实验内容 用一个列表框作为主菜单,通过键盘可以选择功能,主要的功能有:显示bmp 文件、设定系统时间、USB 下载。同时,使用文本框控件作为系统的一个状态条,显示提示信息和系统的时间。 三、预备知识 1、用ARM SDT 2.5 集成开发环境,编写和调试程序的基本过程。 2、基于操作系统的应用程序的框架结构。 3、会使用Source Insight 3 编辑C 语言源程序 4、会使用消息循环响应键盘消息 5、会使用文本框控件和列表框控件 6、会编写通过USB 连接下载的程序 7、会控制系统的时钟和多线
[单片机]
<font color='red'>ARM</font>的综合实验设计
ARM 软中断指令SWI
前面我们学习ARM工作模式中,处理器模式切换可以通过软件控制进行切换,即修改CPSR模式位,但这是在特权模式下,当我们处于用户模式下,是没有权限实现模式转换的。若想实现模式切换,只能由另一种方法来实现,即通过外部中断或是异常处理过程进行切换。于是ARM指令集中提供了两条产生异常的指令,通过这两条指令可以用软件的方法实现异常,其中一个就是中断指令SWI 。 一、软件中断 软中断是利用硬件中断的概念,用软件方式进行模拟,实现从用户模式切换到特权模式并执行特权程序的机制。 硬件中断是由电平的物理特性决定,在电平变化时引发中断操作,而软中断是通过一条具体指令SWI,引发中断操作,也就是说用户程序里可以通过写入SWI指令来切换到特权
[单片机]
<font color='red'>ARM</font> 软中断指令SWI
合并综效疑点重重 软银并ARM案情并不单纯
软体银行(SoftBank)日前宣布将斥资243亿英镑购并安谋国际(ARM),在科技业界引发一阵不小骚动。平心而论,ARM的客户族群中不乏IoT领域的潜力新秀,向来擅向投资购并的软银若能藉由ARM来掌握潜在目标的营运状况,将获得明显战略优势。 安谋国际(ARM)是以处理器和GPU的IP授权业务闻名的公司,而其之所以闻名,主要是因为其在产业的市场地位,超过九成的智慧型手机,以及八成以上的数位相机,并在多样化的嵌入式应用领域都占有相当大的比例。 而软银(SoftBank)提出收购ARM的邀约,之所以这么受世界关注,也就是因为ARM的地位太重要,如果ARM的营运方式有了改变,那么对每年产值达数千亿美元以上的ARM生态相关
[单片机]
ARM汇编语言程序中常用的符号
在汇编语言程序设计中,经常使用各种符号代替地址、变量和常量等,以增加程序的可读性。尽管符号的命名由编程者决定,但并不是任意的,必须遵循以下的约定: — 符号区分大小写,同名的大、小写符号会被编译器认为是两个不同的符号。 — 符号在其作用范围内必须唯一。 — 自定义的符号名不能与系统的保留字相同。 — 符号名不应与指令或伪指令同名。 1、 程序中的变量 程序中的变量是指其值在程序的运行过程中可以改变的量。 ARM ( Thumb )汇编程序所支持的变量有数字变量、逻辑变量和字符串变量。 数字变量用于在程序的运行中保存数字值,但注意数字值的大小不应超出数字变量所能表示的范围。 逻辑变量用于在程序的运行中保存
[单片机]
基于ARM核处理器的机器人手臂控制系统
近年来,随着MEMS及相关技术的发展,微机器人领域已越来越来受人关注。但由于零件的尺寸很小,微机器人组件的装配需要很高的精确度,一般的装配方法无法满足要求。本文介绍了一个可进行微零件装配工作的机器人手臂控制系统的控制方法。 1 系统结构 考虑到多机器人手臂的使用,整个机器人控制系统由上位机与多个下位机组成。下位机即是手臂控制器,每个下位机控制一个机械手臂的伸缩运动。上位机即为控制终端,通过不同配件组装方式生成每个手臂的位置数据,并通过数据线传输给各个下位机,由下位机控制手臂到达目标位置并进行目标操作。整个系统的结构框图如图1所示。 1.1 机械结构 如图2所示,手臂控制器的机械结构由直流减速电机
[单片机]
基于<font color='red'>ARM</font>核处理器的机器人手臂控制系统
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved