源码级和算法级的功耗测试与优化
引言
软件设计中,代码优化是一件非常有意义的事情。优化的本质是对代码进行等价变换,使变换前后的代码运行结果相同,但变换后的代码比变换前的代码具有更多优越性能。传统的观点要求变换后的代码运行速度较快或占用较少运行资源,或二者兼备。随着嵌人式系统的快速发展,软件功耗问题显得越来越重要,应该将“省电”作为软件优化的一项技术指标,这样对软件优化的评价体系才算完整。值得注意的是,大多数情况下性能和功耗并不矛盾,减少程序执行时间同样会使程序功耗减少。
在功耗优化这个问题上,研究者普遍比较关注硬件功耗优化,应用各种技术想方设法改进硬件的功耗,比如在芯片制造工艺上采用更精细的纳米技术,不断降低芯片驱动电压,不断改变片内系统结构等。事实上,整个系统的运行管理是由软件体现的。在硬件基础一定的情况下,只有将软件系统对能量的损耗降至最小,才能使整个系统工作于最佳状态。面向功耗的软件优化方法是当前嵌入式系统低功耗研究领域的热点。掌握软件运行时的能量消耗特征、准确获取能量消耗数据信息,是面向低功耗的软件优化研究的前提。经过多年的努力,许多学者也提出了关于如何减少软件功耗的方法。研究表明,软件优化对降低功耗会有数量级的贡献。针对同一任务,所选择的算法不同或采用不同的实现方式,不仅性能有差别,能耗也大不一样。因此在进行系统优化设计时,除了代码的规模和执行性能之外,功耗也是一个需要认真考虑的问题。
1软件功耗优化方法
常用的软件功耗优化方法大致可分为以下4类:
(1)软件体系结构级
不存在没有体系结构的软件。通常考虑软件体系结构都是注重软件的可修改性、可重用性和可靠性等问题,软件体系结构的好坏直接关系到软件性能的好坏。而且前,关于软件体系结构对软件功耗影响这方面的研究较少,用不同软件体系结构开发出的软件功耗会有差异,怎样选择合适的软件体系结构使软件功耗最小化,将成为软件低功耗优化的重要研究方向。
(2)源程序级
C语言的源程序级功耗优化指的是实现同一功能的不同语句间的选择。比如,同样实现循环功能,有多种选择(for、while、goto等)。这些语句的功耗会有差异,为了实现低功耗的软件,应采用功耗最低的语句来完成相同的功能。这就需要对所有语句的功耗进行测试与研究。同时,对C语言不同数据类型操作的功耗进行分析。比如,同样表示数目,可以用8位int型、16位int型和32位int型。再者,对不同变量的存储类型功耗也要进行分析,如寄存器变量、静态变量、自动变量等。总之,在源码级对软件功耗进行优化是一个重要的研究方向。
(3)算法级
算法是为解决某个特定问题而定义的无二义性的操作序列,算法复杂性分析就是对算法运行时所消耗的计算机资源作量化的分析和预测。以往,程序设计者关心的资源主要是运行时间和存储空间。由于能量消耗已成为软件设计中的关键约束条件,因此本文将能耗作为一项重要资源,对算法运行时所消耗的能量进行分析和比较。
(4)编译级
对于某个硬件来说,执行程序所产生的功耗取决于它的机器代码,而机器代码是从源代码编译而来的,这就说明编译过程也影响了硬件的功耗。既然编译器可以很大程度上控制硬件的运行轨迹,除了性能这一传统的优化目标之外,编译器也可以通过适当的调度优化,使得硬件执行某一个程序时的功耗变小。国际上对于低功耗编译的历史并不长,是从20世纪90年代初才开始研究的,这方面的文章最早出现于文献[4-5],Tiwari等人在这些文章中提出了对软件进行功耗分析的一些基本概念,建立了基本的指令级功耗模型,以486DX为例初步探讨了低功耗编译技术。
本文主要从源程序级和算法级这两个方面对软件功耗特征进行测试与分析,并根据分析结果对μC/OS-II进行源码级的功耗优化。
2源码级和算法级的功耗测试
测试环境是T.K.Tan等人研发的EMSIM,它是一个基于指令级的嵌入式软件功耗模拟器,其主要的功耗估算思想是累计函数中所有单条指令的功耗作为该函数的总功耗。嵌入式硬件平台是ARM公司的StrongARM110。EMSIM测试功耗的单位为函数,即它只能测试某个函数的功耗。在本文的测试中,将要测试的语句放入函数中,测得整个函数的功耗,记为E1,然后测试同样参数及返回值的空语句函数的功耗,记为E2,最后计算得到语句的功耗为:E=E1-E2。
2.1源码级的功耗测试
本小节对C语言的源码级功耗进行测试,测试过程分为以下几步:
①对每种数据类型的不同操作的功耗进行测试。要测试8位整型、16位整型、32位整型以及32位浮点型和64位浮点型的基本操作功耗。基本操作有:加、减、乘、除、取余、赋值、移位、与、或、非。此处测试的结果与处理器的位数有关,StrongAR
从表1可知,对于相同数据类型,加、减、乘、除和取余操作的功耗一样,与、或、非操作的功耗一样,而移位功耗最低。对于不同数据类型来说,32位数比16位数的操作功耗低,16位数比8位数的操作功耗低。32位浮点数与32位整形数的操作功耗一样。在所有数据类型中64位浮点数的操作功耗最高。
测试环境的处理器StrongARM110为32位处理器,对8位数和16位数的处理要考虑字节对齐问题,而对32位就不用考虑该问题。
②对函数、内联函数和宏定义的功耗进行测试。如表2所列,内联函数和宏定义的功耗比一般函数的功耗低,CPU周期数及指令数也小。
③对相同功能不同实现语句的功耗进行测试。主要针对循环语句、选择语句(二元和多元)、乘法、移位、除法、移位进行测试,测试结果如表3所列。
从表3可知,同样实现循环功能,for循环的功耗比while、goto的要大,goto语句的功耗明显最低;二元选择运算中三目运算比if语句的功耗低,但是只能在单条赋值语句中使用三目运算,在复杂的多条语句的情况下,两者功耗一样;多元选择运算中,switch语句比if…elseif…语句功耗低;寄存器变量的操作比自动变量的操作功耗低近50%,因为处理器从寄存器里直接读取变量省去了反复从内存读取变量的过程,从而达到降低功耗的目的。
2.2算法级功耗测试
算法级功耗测试是比源码级功耗测试更高一层次的测试,不同的算法针对同一问题的考虑方面不同,如可靠性、易用性、时间复杂度、空间复杂度、功耗等。以往对软件算法的研究着重在性能上面,本文把功耗作为主要考虑对象,研究算法对功耗的影响。为了便于讨论,本文提出一具体问题,针对该问题提出5种不同的算法,然后分析这些算法对软件功耗的影响。
问题描述:对于1字节的变量v,求其二进制表示中1的个数。
算法1:用除法和取余实现。对于二进制操作,除以2,原来的数将会减少1个0,如果在除的过程中有余,就表示当前位置为1。
算法2:使用与&(即移位>>)操作。&操作,把8位数字v与00000001进行与操作,如果结果为1,表示当前8位的最后1位为1,否则为0,然后再将v右移1位,循环进行。
算法3:使用与&操作,仅考虑v中1的个数。
算法4:使用分支操作,直接把0~255的情况都罗列出来,使用switch…case…,即可得到答案。
算法5:使用查表法,将0~255中1的个数直接存储在数组Array中,v作为数组的下标,则Array[v]就是v中1的个数。
测试结果如表4所列。
其中,M是v中1的个数,log2v为v的位数。
由表4可知,算法1~5的执行效率越来越高,算法5的查表法比算法1节省80%的功耗,其CPU周期数也相应减少,但是它们的指令数却有所增加,所以算法5的查表法是以空间换取时间和功耗的算法。在内存充分大的嵌入式系统中,为尽量降低功耗,算法5是很好的选择。
3 μC/OS-II的源码级功耗优化
μC/OS-II是一种可移植、可固化、可裁减及可剥夺型的多任务实时内核(RTOS),适用于各种微处理器和微控制器。所有代码用ANSI C语言编写,具有良好的可移植性。对μC/OS-II的源码级功耗优化分以下几步实行:
①对计数器数据类型的改进。由表1可知,32位数据类型的加1操作比8位数据类型的加1操作能耗低27nJ,将μC/OS-II中常用数据的数据类型改为INT32U,如任务控制块OS_TCB中的prio、OSTCBDly、OSTCBX、OSTCBY、OSTCBBitX、OSTCBBitY等。
②对循环控制语句的改进。由表3可知,while、goto循环语句的功耗比for循环语句的功耗低。将μC/OS-II中for循环句换成while循环语句,经查看μC/OS-II的源码,发现μC/OS-II在设计时已考虑到该问题,多数循环使用while实现。在此只对OSInit()函数改进,同时μC/OS-II中固定的任务(如OS_TaskIdle、OS_TaskStat中的控制)改为goto语句,减少应用程序的功耗。
③对内联函数和宏的使用。对简短的常用函数加上inline关键字,或用宏来实现,内联函数和宏的使用使软件功耗降低。读RAM比读Flash功耗更大。处理器进入子程序时,会首先将当前处理器的寄存器推入堆栈(RAM),在离开时又将处理器的寄存器弹出堆栈,这样至少两次对RAM操作。而宏在编译时展开,处理器顺序执行指令,避免了调用子程序,同时减少了系统的功耗。μC/OS-II中常用的短函数改为内联函数,如每个时钟都要执行的OSTimeTick()和开关中断等,同时μC/OS-II中采用条件编译,也会在一定程度上降低功耗。
④对变量存储类型的优化。对于大部分嵌入式系统来说,为了提高运行速度,通常寄存器做得很大,如ARM系列处理器有31个通用寄存器。有时许多寄存器空着没使用,可以将程序中常用的常量或变量直接置于寄存器中,而不是置于内存的静态存储区或动态存储区中。这样做不仅提高了软件运行速度,而且也节省能量消耗。由表3可知,使用寄存器变量能省近50%的功耗,μC/OS-II中每个时钟周期都要使用的计数变量OSTime,将其用关键字register声明即可。还有循环控制语句的计数变量,将其声明为寄存器变量,降耗效果明显。
⑤算法级的改进。从算法级功耗的算法5可以看出,将一些运算的结果预先算好,放在Flash中,用查表的方法替代实时的计算,减少微控制器的运算工作量,可以有效地降低微控制器的功耗;不可避免的实时计算,达到精度就结束,避免“过度”计算;在精度允许的情况下,使用简单函数代替复杂函数作近似,也可以减少功耗。μC/OS-II中的任务调度和事件管理模块都采用查找就绪表的方式来提高性能和降低功耗。为此,针对μC/OS-II的内存管理机制采用查表算法,借用任务管理中的就绪表实现内存块的分配,这样不但不会增加额外的空间需求,而且使内存管理的功耗更低。
对μC/OS-II的部分功能函数进行源码级功耗优化,其优化前后的结果如图1所示。图中,纵轴表示能耗(nJ),横轴表示改进前后的功能函数。
结语
功耗较大的软件,使用了较多功耗大的操作指令或是使用了不必要的指令。本文的创新之处在于,对软件功耗优化中的源码级和算法级的功耗优化进行分析,对用不同语句实现相同功能的情况进行分类讨论,测试其功耗特征,最后将功耗测试与分析结果运用到嵌入式操作系统μC/OS-II中,对其进行源码级的功耗优化,实验结果证明,源码级的功耗优化能明显降低软件的功耗。
评论
查看更多