程序设计优化

(1)、求余运算。
a=a%8;
能够改成:
a=a&7;
说明:位操做只需一个指令周期便可完成,而大部分的C编译器的“%”运算均是调用子程序来完成,代码长、执行速度慢。一般,只要求是求2n方的余数,都可使用 位操做的方法来代替。html

2)、用移位实现乘除法运算
a=a*4;
b=b/4;
能够改成:
a=a<<2;
b=b>>2;算法

a=a*9
能够改成:
a=(a<<3)+a数组

3)使用尽可能小的数据类型
4)使用自加、自减指令
http://blog.chinaunix.net/uid-20361370-id-1962787.html
http://wenku.baidu.com/view/917c9fd6195f312b3169a564.html?re=view网络

ARM 相关
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0198e/ch05s02s03.htmljsp

http://www.atarm.com/doc/Efficient_programming_techniques_for_ARM.pdf
一篇讲ARM优化的文章,不错
http://blog.csdn.net/shengnan_wu/article/details/8287428
展开:
下面是网络上收集关于ARM的C代码优化方法,在嵌入开发中应该有用:函数

[声明:如下方法非本人发现和总结,均为有心之人无私贡献,谢谢他们的劳动与分享!]布局

========================================================================================post

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取值范围的状况而添加代码。

另外对于表达式的处理也要格外当心,以下例子

[html] view plaincopy
01.short checksum_v3(short * data){

  1. unsigned int i;
  2. short sum = 0;
  3. for(i = 0; i < 64 ; i++){
  4. sum = (short)( sum + data );
  5. //这里表达式式整形的,因此返处理非32位数据时,
  6. //要当心处理数据类型的转换。
  7. //原来short+short=int 但 int +int=int。。奇怪的处理
  8. }
  9. return sum;
    11.}
    同时如上例的程序所示,这样在循环体中的每次运算都要进行类型转换,会下降程序的效率,能够先把其看成int来运算,而后再返回一个short类型。
    同时,因为处理的data[]是一个short型数组,用LDRH指令的话,不能使用桶型移位器,因此只能先进行偏移量的觉得操做,而后再寻址,也会形成不佳的性能。解决的方法是用指针代替数组操做。以下:

[html] view plaincopy
01.short checksum_v4(short * data){

  1. unsigned int i;
  2. int sum = 0;
  3. for( i = ; i<64; i++) {
  4. sun += ( data ++);
  5. }
  6. return (short) sum;
    08.}

  7. 关于函数参数类型
    函数参数和返回值应尽可能使用int类型。
    另外,对于调用频率较低的全局变量,尽可能使用小的数据类型以节省空间。

C循环结构

◎ 使用减数到零的循环体,以节省指令和寄存器的使用。
◎ 使用无符号的循环计数值,并用条件 i != 0停止。
◎ 若是循环体至少执行一次,用优先选用do-while。
◎ 适当状况下展开循环体。
◎ 尽可能使用数组的大小是4或8的备述,用此倍数展开循环体 寄存器分配
◎ 尽可能限制函数内部循环所用局部变量的数目,最多不超过12个,以便编译器能把变量分配到寄存器。

◎ 能够引导编译器,经过查看是否属于最内层循环的便赖宁嘎来去定某个变量的重要性。

函数调用

ARM中的函数前4个整型参数经过寄存器r0、r一、r二、r3来传递,随后的整型参数经过堆栈来传递。(full desceding stack)。

◎ 尽可能限制函数参数,不要超过四个,也能够把相关的参数组织在结构体传递。
◎ 把比较小的被调用函数和调用函数放在同一个源文件中,而且限定之后调用,编译器能进行优化。
◎ 用_inline内联性能影响较大的重要函数。

指针别名

◎ 用一个局部变量来保存公共子表达式的值,保证该表达式只求一次值。
◎ 避免使用局部变量的地址,不然访问这个变量的效率较低。

结构体的安排

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

位域

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

除法

◎ 一堆算法,很差写,总的来讲是以乘代除,配合移位运算。 内联函数和内嵌汇编
◎ 没什么好写的,就是内联减小调用开销,内嵌汇编提升运行效率。

总结

总的来讲,高级语言的优化和编译器、硬件结构有关。

硬件上,ARM通常为32位总线,以32位访问数据的速度较快。局部变量和其余经常使用的变量要尽可能利用32位的int类型,组织结构体时,也要注意元素的位置(小前大后),以节省空间。另外,因为ARM指令可条件执行,因此充分利用cpsr会使程序更有效率。同时注意好类型之间的运算,尽可能减小转型操做。任什么时候候除法和取模运算能够同时取得结果而不会额外增长运算过程,但单单对于除法,仍是以乘代除比较划算。

对于编译器,armcc听从ATPCS的要求,第一到第四个参数依次经过r0~r4传递,其余参数经过堆栈传递,返回值用r0传递,所以,为了把大部分操做放在寄存器中完成,参数最好很少与4个。另外,可用的通用寄存器有12个,因此尽可能将局部变量控制在12个以内,效率上会获得提高。同时,因为编译器比较保守,指针别名会引发多余的读操做,因此尽可能少用。

============================================================================================

数据类型

存放在寄存器中的局部变量(尤为是循环变量)应尽量使用32位数据类型int(=long),8位变量不节省任何空间和时间;

即便传输一个8位数据,函数参数和返回值使用32位类令会更有效;

能用指针递增寻址就不用数组下表递增寻址a=data[i++]不如a=*(data++);

除法运算使用无符号数更快;

存放在存储器中的数组和全局变量,尽量使用小尺寸数据类型;

short型数组尽可能避免使用数组基地址的偏移量,由于LDRH指令不支持偏移寻址;

存储器变量和寄存器变量相互赋值时使用显式类型转换,其余状况下避免没必要要的类型转换;

循环结构

采用减计数循环比增计数循环更好,终止条件尽可能写 i != 0 ;循环变量起始值是变量且不等于0的状况下用do-while循环更优(终止条件在后);

若循环体过于简单,好比少于4个周期,可展开循环体(重复写几遍循环体代码),以避免循环体代码还不如循环自己执行周期长;

尽可能限制函数内部循环所用局部变量的数据,最多不要超过12个,这样编译器就能够把他们都分配给ARM寄存器;

函数调用

尽可能限制函数的参数,不要超过4个。能够将几个相关参数组织在一个结构体中;

把较小的被调函数和调用函数放在一个文件中,并先定义再调用;

对性能影响较大的重要函数可以使用_inline进行内联;

指针别名

创建一个新的局部变量来保存包含存储器访问的表达式,这样能够保证只对这个表达式求一次值,例如int a=data[n];b+=a;c+=a; 比b+=data[n];c+=data[n];好避免使用局部变量的地址,不然对这个变量的访问效率会比较低;
结构体安排

结构体元素要按照元素从小到大排序;
避免使用很大的结构体,能够用层次化的小结构体来代替

注:针对ARMv4以上版本

===========================================================================================
变量定义

32位ARM处理器的指令集支持有符号/无符号的8位、16位、32位整型和浮点型变量类型,这不只能够节省代码,并且能够提升代码的运行效率。按照做用范围的不一样,C语言的变量能够划分为全局变量和局部变量。ARM编译器一般将全局变量定位在存储空间中,局部变量分配给通用寄存器。

在全局变量声明时,须要考虑最佳的存储器布局,使得各类类型的变量能以32位的空间位基准对齐,从而减小没必要要的存储空间浪费,提升运行效率。如:

01.char a; char a;
02.short b; char c;
03.char c; short b;
04.int d; int d;

对于局部变量,要尽可能不使用32位之外的变量类型。当一个函数的局部变量数目很少时,编译器会把局部变量分配给内部寄存器,每一个变量占一个32位的寄存器。这样short和char类型的变量不但起不到节省空间的做用,反而会耗费更多的指令周期来完成short和char的存取操做。C语言代码及其编译结果以下所示:

01.int wordinc(int a) wordinc:
02.{

  1. return (a+1); ADD a1, a1,#1
    04.}
  2. 06.short shortinc(short a) shortinc:
    07.{ ADD a1, a1,#1
  3. return a+1; MOV a1, a1, LSL #16
    09.} MOV a1, a1, ASR #16
  4. MOV  PC,LR
  5. 12.char charinc(char a) charinc:
    13.{ ADD a1, a1,#1
  6. return a+1; AND a1, a1,#&FF
    15.} MOV PC,LR
    条件执行
    条件执行是程序中必不可少的基本操做。典型的条件执行代码序列是由一个比较指令开始的,接下来是一系列相关的执行语句。ARM中的条件执行是经过对运算结果标志位进行判断实现的,一些带标志位的运算结果中,N和Z标志位的结果与比较语句的结果相同。尽管在C语言中没有带标志位的指令,但在面向ARM的C语言程序中,若是运算结果是与0做比较,编译器会移去比较指令,经过一条带标志位指令实现运算和判断。例如:

01.int g(int x, int y) g:
02.{ ADDS a1, a1, a2

  1. if(x+y<0) MOVPL a1, #0
  2. return 1; MOVMI a1, #1
  3. else MOV pc, lr
  4. return 0;
    07.}
    所以,面向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语言程序设计中,关系表述中相似的条件应该集中在一块儿,使编译器可以对判断条件进行优化。
    循环

循环是程序设计中很是广泛的结构。在嵌入式系统中,微处理器执行时间在循环中运行的比例较大,所以关注循环的执行效率是很是必要的。除了在保证系统正确工做的前提下尽可能简化核循环体的过程之外,正确和高效的循环结束标志条件也很是重要。按照以上所述的“与0比较”原则,程序中的循环结束条件应该是“减到0”的循环,结束条件尽可能简单。应尽量在关键循环中采起上述的判断形式,这样能够在关键循环中省去一些没必要要的比较语句,减小没必要要的开销,提升性能。以下面二个示例:

01.int fact1(int n) int fact2(int n)
02.{ {

  1. int a, fact=1; int a, fact=1;
  2. for(a=1; a<=n; a++) for(a=n; a!=0; a++)
  3. fact *= a;                       fact *= a;
  4. return(fact); return(fact);
    07.} }
    fact1和fact2中经过定义局部变量a来减小对n的load/store操做。fact2函数遵循了“与0比较”原则,省去了fact1编译结果中的比较指令,而且,变量n在整个循环过程不参与运算,也不须要保存。因为省去了寄存器分配,从而给其余部分程序的编译带来了方便,提升了运行效率。

“减到0”的方法一样适用于while和do语句。若是一个循环体只循环几回,能够用展开的方法提升运行效率。当循环展开后,不须要循环计数器和相关的跳转语句,虽然代码的长度有所增长,可是获得了更高的执行效率。

除法和求余

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

Time(分子/分母)=C0+C1×log2(分子/分母)
=C0+C1×(log2(分子)-log2(分母))
因为除法的执行周期长,耗费的资源多,程序设计中应当尽可能避免使用除法。如下是一些避免调用除法的变通办法:

(1)在某些特定的程序设计时,能够把除法改写为乘法。例如:(x/y)>z,在已知y是正数并且y×z是整数的状况下,就能够写为x>(z×y)。

(2)尽量使用2的次方做为除数,编译器使用移位操做完成除法,如128就比100更加适合。在程序设计中,使用无符号型的除法要快于符号型的除法。

(3)使用求余运算的一个目的是为了按模计算,这样的操做有时可使用if的判断语句来完成,考虑以下的应用:

(4)对于一些特殊的除法和求余运算,采用查找表的方法也能够得到很好的运行效果。

在除以某些特定的常数时,编写特定的函数完成此操做会比编译产生的代码效率高不少。ARM的C语言库中就有二个这样的符号型和无符号型数除以10的函数,用来完成十进制数的快速运算。在toolkit子目录的examples\explasm\div.c和examples\thumb\div.c文件中,有这二个函数的ARM和Thumb版本。

==========================================================================================

1 程序运行速度优化

程序运行速度优化的方法可分为如下ARM几大类。

1.1 通用的优化方法

(1)减少运算强度

利用左/ 右移位操做代替乘/ 除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 来代替。

(2)优化循环终止ARM条件

在一个循环结构中,循环的终止条件将严重影响着循环的效率,再加上ARM 指令的条件执行特性,因此在书写循环的终止条件时应尽可能使用count-down-to-zero结构。这样编译器能够用一条BNE (若非零则跳转)指令代替CMP (比较)和BLE (若小于则跳转)两条指令,既减少代码尺寸,又加快了运行ARM速度。

(3)使用inline 函数

ARM C 支持 inline 关键字,若是一个函数被设计ARM成一个inline 函数,那么在调用它的地方将会用函数体来替代函数调用语句, 这样将会完全省去函数调1.2 处理器相关的优化ARM方法用的开销。使用inline 的最大缺点是函数在被频繁调用时,代码量将增大。

(1)保持流水线畅通

从前面的介绍可知,流水线延迟或阻断会对处理器的性能形成影响,所以应该尽可能保持流水线畅通。流水线延迟难以免, 但能够利用延迟周期进行其它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)的例子。

流水线阻断的状况可经过循环拆解等方法加以改善。一个循环能够考虑拆解以减少跳转指令在循环指令中所占的比重, 进而提升代码效率。下面以一个内存复制函数加以ARM说明。

01.void memcopy(char to, char from, unsigned int nbytes)
02.{

  1. while(nbytes--)ARM
  2. *to++ = *from++;
    05.}
    为简单起见,这里假设nbytes 为16 的ARM倍数(省略对余数的处理)。上面的函数每处理一个字节就要进行一次判断和跳转, 对其中的循环体可做以下拆解:

01.void memcopy(char to, char from, unsigned int nbytes)
02.{

  1. while(nbytes) {
  2. *to++ = *from++;
  3. *to++ = *from++;ARM
  4. *to++ = *from++;
  5. *to++ = *from++;
  6. nbytes - = 4;
  7. }
    10.}
    这样一来, 循环体中的指令数增长了,循环次数却减小了。跳转指令ARM带来的负面影响得以削弱。利用ARM 7 处理器32 位字长的特性, 上述代码可进一步做以下调整:

01.void memcopy(char to, char from, unsigned int nbytes)ARM
02.{

  1. int p_to = (int )to;
  2. int p_from = (int )from;
  3. while(nbytes) {
  4. *p_to++ = *p_from++;
  5. *p_to++ = *p_from++;
  6. *p_to++ = *p_from++;
  7. *p_to++ = *p_from++;
  8. nbytes - = 16;
  9. }
    12.}
    通过优化后,一次循环能够处理16 个字节。跳转指令带来的影响ARM进一步获得减弱。不过能够看出, 调整后的代码在代码量方面有所增长。

(2)使用寄存器变量

CPU 对寄存器的存取要比对内存的存取快得多ARM, 所以为变量分配一个寄存器, 将有助于代码的优化和运行效率的提升。整型、指针、浮点等类型的变量均可以分配寄存器; 一个结构的部分或者所有也能够分配寄存器。给循环体中须要频繁访问的变量分配寄存器也能在必定程度上提升程序效率。

1.3 指令集相关的优化方法

有时能够利用ARM7 指令集的特色对程序ARM进行优化。

(1)避免除法

ARM 7 指令集中没有除法指令,其除法是经过调用C 库函数实现的。一个32 位的除法一般须要20~140 个时钟周期。所以, 除法成了一个程序效率的瓶颈, 应尽可能避免使用。有些除法可用乘法代替,例如: if ( (x / y) > z)可变通为 if ( x > (y × z)) 。在能知足精度,且存储器空间冗余的状况下, 也可考虑使用查表法代替除法。当除数为2 的ARM幂次方时, 应用移位操做代替除法。

(2)利用条件执行

ARM 指令集的一个重要特征就是全部的指令都可包含一个可选的条件码。当程序状态寄存器(PSR )中的条件码标志知足指定条件时, 带条件码的指令才能执行。利用条件执行一般能够省去单独的判断ARM指令,于是能够减少代码尺寸并提升程序效率。

(3)使用合适的变量类型

ARM 指令集支持有符号/ 无符号的8 位、16 位、32位整型及浮点型变量。恰当的使用变量的类型,不只能够节省代码,而且能够提升代码运行效率。应该尽量地避免使用char、short 型的ARM局部变量,由于操做8 位/16 位局部变量每每比操做3 2 位变量须要更多指令, 请对比下列3 个函数和它们的汇编代码。

1.4 存储器相关的优化方法

(1)用查表代替计算

在处理器资源紧张而存储器资源相对富裕的状况下, 能够用牺牲存储空间换取运行速度的办法。例如须要频繁计算正弦或余弦函数值时,可预先将函数值计算出来置于内存中供之后ARM查找。

(2)充分利用片内RAM

一些厂商出产的ARM 芯片内集成有必定容量的RAM,如Atmel 公司的AT91R40807 内有128KB 的RAM,夏普公司的LH75400/LH75401 内有32KB 的RAM。处理器对片内RAM 的访问速度要快于对外部RAM 的访问,因此应尽量将程序调入片内RAM 中运行。若因程序太大没法彻底放入片内RAM ,可考虑ARM将使用最频繁的数据或程序段调入片内RAM 以提升程序运行效率。

1.5 编译器相关的优化方法

多数编译器都支持对程序速度和程序大小的优化,有些编译器还容许用户选择可供优化的内容及优化的程度。相比前面的各类优化方法, 经过设置编译器选项对程序进行优化不失为一种简单有效的途径。

2 代码尺寸优化

精简指令集计算机的一个重要特色是指令长度固定, 这样作能够简化指令译码的过程,但却容易致使代码尺寸增长。为避免这个问题,能够考虑采起如下措施来缩减程序ARM代码量。

2.1 使用多寄存器操做指令

ARM 指令集中的多寄存器操做指令LDM/STM 能够加载/ 存储多个寄存器,这在保存/ 恢复寄存器组的状态及进行大块数据复制时很是有效。例如要将寄存器R4~R12 及R14 的内容保存到堆栈中,若用STR 指令共须要10 条,而一条STMEA R13!, {R4 ?? R12, R14} 指令就能达到相同的目的,节省的指令存储空间至关可观。不过须要注意的是, 虽然一条LDM/STM 指令能代替多条LDR/STR 指令,但这并不意味着程序运行速度获得了ARM提升。实际上处理器在执行LDM/STM 指令的时候仍是将它拆分红多条单独的LDR/STR 指令来执行

2.2 合理安排变量顺序

ARM 7 处理器要求ARM程序中的32 位/16 位变量必须按字/ 半字对齐,这意味着若是变量顺序安排不合理, 有可能会形成存储空间的浪费。例如:一个结构体中的4个32 位int 型变量i1 ~ i4 和4 个8 位char 型变量c1 ~ c4,若按照i一、c一、i二、c二、i三、c三、i四、c4 的顺序交错存放时, 因为整型变量的对齐会致使位于2 个整型变量中间的那个8 位char 型变量实际占用32 位的存储器,这样就形成了存储空间的浪费。为避免这种状况, 应将int 型变量和char 型变量按相似i一、i二、i三、i四、c一、c二、c三、c4 的顺序连续存放。

2.3 使用Thumb 指令

为了从根本上有效ARM下降代码尺寸,ARM 公司开发了16 位的Thumb 指令集。Thumb 是ARM 体系结构的扩充。Thumb 指令集是大多数经常使用32 位ARM 指令压缩成16 位宽指令的集合。在执行时,16 位指令透明的实时解压成32 位ARM 指令并无性能损失。并且程序在Thumb状态和ARM 状态之间切换是零开销的。与等价的32 位ARM 代码相比,Thumb 代码节省的存储器空间可高达35% 以上。

相关文章
相关标签/搜索