软件开发的家园,编程爱好者的天地.

现在是:北京时间 2016/4/14 上午11:50:51 星期四

设为首页  |  加入收藏  |  网站地图

ARM 程序设计优化策略与技术
发布于:第八基地 来源:互联网 作者:天堂路上 时间:2011-10-10 点击:221

 程序优化是指软件编程结束后,利用软件开发工具对程序进行调整和改进,让程序充分利用资源,提高运行效率,缩减代码尺寸的过程。按照优化的侧重点不同,程序优化可分为运行速度优化和代码尺寸优化。运行速度优化是指在充分掌握软硬件特性的基础上,通过

  应用程序结构调整等手段来降低完成指定任务所需执行的指令数。在同一个处理器上,经过速度优化的程序比未经优化的程序在完成指定任务时所需的时间更短,即前者比后者具有更高的运行效率。代码尺寸优化是指,采取措施使应用程序在能够正确完成所需功能的前提下,尽可能减少程序的代码量。

  然而在实际的程序设计过程中,程序优化的两个目标(运行速度和代码大小)通常是互相矛盾的。为了提高程序运行效率,往往要以牺牲存储空间、增加代码量为代价,例如程序设计中经常使用的以查表代替计算、循环展开等方法就容易导致程序代码量增加。而为了减少程序代码量、压缩存储器空间,可能又要以降低程序运行效率为代价。因此,在对程序实施优化之前,应先根据实际需求确定相应的策略。在处理器资源紧张的情况下,应着重考虑运行速度优化;而在存储器资源使用受限的情况下,则应优先考虑代码尺寸的优化。

  1程序运行速度优化

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

  1.1通用的优化方法

  (1)减小运算强度

  利用左/右移位操作代替乘/除2运算:通常需要乘以或除以2的幂次方都可以通过左移或右移n位来完成。实际上乘以任何一个整数都可以用移位和加法来代替乘法。arm7中加法和移位可以通过一条指令来完成,且执行时间少于乘法指令。例如:i=i×5可以用i=(i<<2)+i来代替。

  利用乘法代替乘方运算:arm7核中内建有32×8乘法器,因此可以通过乘法运算来代替乘方运算以节约乘方函数调用的开销。例如:i=pow(i,3.0)可用i=i×i×i来代替。

  利用与运算代替求余运算:有时可以通过用与(AND)指令代替求余操作(%)来提高效率。例如:i=i%8可以用i=i&0x07来代替。

  (2)优化循环终止条件

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

  (3)使用inline函数

  armC支持inline关键字,如果一个函数被设计成一个inline函数,那么在调用它的地方将会用函数体来替代函数调用语句,这样将会彻底省去函数调用的开销。使用inline的最大缺点是函数在被频繁调用时,代码量将增大。

  1.2处理器相关的优化方法

  (1)保持流水线畅通

  从前面的介绍可知,流水线延迟或阻断会对处理器的性能造成影响,因此应该尽量保持流水线畅通。流水线延迟难以避免,但可以利用延迟周期进行其它操作。

  LOAD/STORE指令中的自动索引(auto-indexing)功能就是为利用流水线延迟周期而设计的。当流水线处于延迟周期时,处理器的执行单元被占用,算术逻辑单元(ALU)和桶形移位器却可能处于空闲状态,此时可以利用它们来完成往基址寄存器上加一个偏移量的操作,

  供后面的指令使用。例如:指令LDRR1,[R2],#4完成R1=*R2及R2+=4两个操作,是后索引(post-indexing)的例子;而指令LDRR1,[R2,#4]!完成R1=*(R2+4)和R2+=4两个操作,是前索引(pre-indexing)的例子。

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

  voidmemcopy(char*to,char*from,unsignedintnbytes)

  {

  while(nbytes--)

  *to++=*from++;

  }

  为简单起见,这里假设nbytes为16的倍数(省略对余数的处理)。上面的函数每处理一个字节就要进行一次判断和跳转,对其中的循环体可作如下拆解:

  voidmemcopy(char*to,char*from,unsignedintnbytes)

  {

  while(nbytes){

  *to++=*from++;

  *to++=*from++;

  *to++=*from++;

  *to++=*from++;

  nbytes-=4;

  }

  }

  这样一来,循环体中的指令数增加了,循环次数却减少了。跳转指令带来的负面影响得以削弱。利用arm7处理器32位字长的特性,上述代码可进一步作如下调整:

  voidmemcopy(char*to,char*from,unsignedintnbytes)

  {

  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个字节。跳转指令带来的影响进一步得到减弱。不过可以看出,调整后的代码在代码量方面有所增加。

  (2)使用寄存器变量

  CPU对寄存器的存取要比对内存的存取快得多,因此为变量分配一个寄存器,将有助于代码的优化和运行效率的提高。整型、指针、浮点等类型的变量都可以分配寄存器;一个结构的部分或者全部也可以分配寄存器。给循环体中需要频繁访问的变量分配寄存器也能在

  一定程度上提高程序效率。

  1.3指令集相关的优化方法

  有时可以利用arm7指令集的特点对程序进行优化。

  (1)避免除法

  arm7指令集中没有除法指令,其除法是通过调用C库函数实现的。一个32位的除法通常需要20~140个时钟周期。因此,除法成了一个程序效率的瓶颈,应尽量避免使用。有些除法可用乘法代替,例如:if((x/y)>z)可变通为if(x>(y×z))。在能满足精度,且存储器空间

  冗余的情况下,也可考虑使用查表法代替除法。当除数为2的幂次方时,应用移位操作代替除法。

  (2)利用条件执行

  arm指令集的一个重要特征就是所有的指令均可包含一个可选的条件码。当程序状态寄存器(PSR)中的条件码标志满足指定条件时,带条件码的指令才能执行。利用条件执行通常可以省去单独的判断指令,因而可以减小代码尺寸并提高程序效率。

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

  arm指令集支持有符号/无符号的8位、16位、32位整型及浮点型变量。恰当的使用变量的类型,不仅可以节省代码,并且可以提高代码运行效率。应该尽可能地避免使用char、short型的局部变量,因为操作8位/16位局部变量往往比操作32位变量需要更多指令,请对比下列3个函数和它们的汇编代码。

  intwordinc(inta)wordinc

  {ADDa1,a1,#1

  returna+1;MOVpc,lr

  }shortinc

  shortshortinc(shorta)ADDa1,a1,#1

  {MOVa1,a1,LSL#16

  returna+1;MOVa1,a1,ASR#16

  }MOVpc,lr

  charcharinc(chara)charinc

  {ADDa1,a1,#1

  returna+1;ANDa1,a1,#&ff

  }MOVpc,lr

  可以看出,操作32位变量所需的指令要少于操作8位及16位变量。

  1.4存储器相关的优化方法

  (1)用查表代替计算

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

  (2)充分利用片内RAM

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

  1.5编译器相关的优化方法

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

  2代码尺寸优化

  精简指令集计算机的一个重要特点是指令长度固定,这样做可以简化指令译码的过程,但却容易导致代码尺寸增加。为避免这个问题,可以考虑采取以下措施来缩减程序代码量。

  2.1使用多寄存器操作指令

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

  2.2合理安排变量顺序

  arm7处理器要求程序中的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的顺序连续存放。

  2.3使用Thumb指令

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

  结语

  综上所述,优化的过程是在透彻了解软/硬件结构和特性的前提下,充分利用硬件资源,不断调整程序结构使之趋于合理的过程。其目的是最大程度发挥处理器效能,最大限度利用资源,尽可能提高程序在特定硬件平台上的性能。随着ARM处理器在通信及消费电子等行业中的应用日趋广泛,优化技术将在基于arm处理器的程序设计过程中发挥越来越重要的作用。

  值得注意的是,程序的优化通常只是软件设计需要达到的诸多目标之一,优化应在不影响程序正确性、健壮性、可移植性及可维护性的前提下进行。片面追求程序的优化往往会影响健壮性、可移植性等重要目标。

对我有帮助
(0)
0%
对我没帮助
(0)
0%
返回顶部
在线反馈
在线反馈