原创 算法优化的重要性

2010-9-19 16:06 3990 11 14 分类: MCU/ 嵌入式

                    算法优化的重要性


 


前些日子,在开发一个单片机的产品时,遇到这么一个问题,就是要将一个无符号的16位数,乘以1.2288后,再赋给另一个无符号的16位数。用C语言可以描述如下:


INT16U x,y;


y = (INT16U)((float)x * 1.2288);    // 注:x的取值范围是500到16000


由于单片机采用的NXP的LPC762,属于51系列的,主频为7.3728MHZ,因此速度较慢。在KeilC51环境中,通过模拟运行,可以得出,运行上述C语句需要的时间为406uS。虽然这个时间比我想象的要快些,但不能满足产品的实时性要求。


由于不能慢足要求,因此我将浮点预算改成了定点运算,即将1.2288变成了12288/10000,这样上述语句就改写成如下:


y = (INT16U)((INT32U)x * (INT32U)12288 / (INT32U)10000);


结果实在是令人遗憾,执行时间不但没降,反而上升到623uS,比浮点运算更慢,估计是增加了一个除法运算引起来的。


无论是定点和浮点运算,时间都是几百微秒,都不能满足产品的实时性要求。因此只能另想办法。


在上面的定点运算中,虽然理论上说定点运算比浮点运算要快,但由于多了定点除法,导致了最终时间不但没有达到要求,而且更慢。因此就沿着这个思路,想到了用分数来近似表示一个浮点数的办法,同时要求除法要快。


所谓除法要快,就是考虑将除法变成移位操作,比如说,除以2,就相当于右移1位,除以4,就相当于右移2位,如果除以65536,就相当于右移16位,或者说直接丢弃低16位。通过这种分析,就可以将1.2288用分数近似表示成80530/65536,实际值为1.228790283203125,相对误差为(1.2288-80530/65536)/1.2288=0.0000079,应该说非常小了。由于x的取值范围是500到16000,绝对误差的最大值发生在x取16000时,此时的误差计算如下:


16000 * 1.2288 – 16000 * 80530/65536 = 19660 – 19660 = 0


可以说在x的取值范围内,没有误差。


那么这样的近似表示的好处是什么呢?答案是只要进行一次定点的乘法,得到一个无符号的32位数,然后直接丢弃低16位,高16位的数就是y。


写成C语句就是:


y = ((INT32U)x * 80530)>>16;


运行上述语句的时间为298uS。很明显,这样的算法比采用浮点运算的时间减少了108uS(相对于减少了26%)。因此可以说,算法在产品的开发中是非常重要的。


为了进一步减少运行的时间,考虑到一个32位的无符号数右移16位后赋给16位的无符号数,在汇编语言中非常容易实现,因此,决定用汇编实现上述语句,在C语言中的语句如下:


y = FUNC(x);


其中FUNC是用汇编语言写的函数,具体的代码如下,在汇编代码中,为了快速实现定点乘法,没有去调用编译器提供的库,而是根据这个具体的应用做了优化,以提高代码的运行速度(也就是说,还是算法的问题)


在汇编代码中,要根据具体问题的特点,进一步进行算法上的优化。在上述公式中,80530=65536+14994,这样上面的语句就可以改写成:y=x+x*14994/65536。


这样就可以将乘法变成了2个16位的无符号数的乘法,从而提高代码的运行速度。对2个16位的无符号数的乘法,分析如下:


假设一个16位的无符号数x1,其高8位和低8位表示成a1和b1,则x1 = a1 * 256 + b1。同样,另一个无符号数x2,也可以表示成x2 = a2 * 256 + b2。


x1 * x2 =(a1 * 256 + b1) * (a2 * 256 + b2)


       = a1 * a2 *65536 + (a1 * b2 + a2 * b1) *256 + b1 * b2


通过这样的分解,就可以用51的乘法指令直接运算出a1*a2、a1*b2、a2*b1和b1*b2,而不用采用移位的办法,这样也就提高了运算的速度。另外,由于最终的这个数要除以65536,因此在计算完b1*b2后,可以将低8位直接丢弃,保留高8位,与(a1 * b2 + a2 * b1)*256运算。总之,通过汇编指令的优化,可以大大提高运算速度。下面是FUNC的详细代码,通过在KeilC51环境中的模拟执行,可以得出,该函数的运行时间为49uS。这个时间只有最初采用浮点数时的十分之一左右,可见算法的优化对一个系统来说是多么重要。


 


 


       PUBLIC _FUNC


; unsigned int FUNC(unsigned int x)


; 下面的函数完成的功能:


; x * 1.2288 = x + x * 0.2288 = x + x * 14994 / 65536


; 14944用十六进制数表示为3A92H


       RSEG  ?PR?_FUNC?SUB


_FUNC:


       USING   0


; 入口时x已经在寄存器(R6,R7)中


; 第一步实现:(0,R5)<- (R7 * 92H)的高8位


        MOV     B,#92H


           MOV     A,R7


           MUL     AB


        MOV     R5,A          ;只需要保留高8位的结果


; 第二步实现:(R4,R5) <- R7 * 3AH + (R7 * 92H)的高8位


        MOV     B,#3AH


           MOV     A,R7


           MUL     AB            ;结果在(B,A)中


;下面实现(R4,R5)<-(0,R5) + (B,A)


       ADD     A,R5


           MOV     R5,A


       MOV     A,#00H


           ADDC    A,B


     MOV     R4,A


; 第三步实现:(R4,R5) <- (R6 * 92H + R7 * 3A + (R7 * 92H)的高8位)的高8位和进位


        MOV     B,#92H


       MOV     A,R6


       MUL     AB            ; 结果在(B,A)中


        ADD     A,R5          ;加低8位,目的是要进位位


           MOV     A,B          


       ADDC    A,R4         


        MOV     R5,A          ;保留高8位的运算结果


        RLC     A


        ANL     A,#01H


        MOV     R4,A          ;将进位位保存到R2的最低位中


; 第四步实现:(R4,R5) <- R6 * 3A + (R6 * 92H + R7 * 3A + (R7 * 92H)的高8位)的高8位和进位


           MOV     B,#3AH


       MOV     A,R6


           MUL     AB            ; 结果在(B,A)中


       ADD     A,R5         


        MOV     R5,A


        MOV     A,B


        ADDC    A,R4


        MOV     R4,A          ; 到此(R4,R5) <- x * 14994 / 65536


; 第五步实现:x + x * 0.2288


        MOV     A,R5


       ADD     A,R7


           MOV     R7,A


       MOV     A,R4


           ADDC    A,R6


       MOV     R6,A


           RET    


 


 


 


 


                     2010-9-19


 

PARTNER CONTENT

文章评论3条评论)

登录后参与讨论

用户224505 2010-9-23 09:50

写成C语句就是: y = ((INT32U)x * 80530)>>16; 我觉得写成 y =((INT16U)x*14995)>>16+(INT16U)x;更好, 理由:1、只使用INT16 2、14995*x/65536 舍去小数点后就是x*0.2288 而用14994不是的

用户224505 2010-9-23 09:50

写成C语句就是: y = ((INT32U)x * 80530)>>16; 我觉得写成 y =((INT16U)x*14995)>>16+(INT16U)x;更好, 理由:1、只使用INT16 2、14995*x/65536 舍去小数点后就是x*0.2288 而用14994不是的 没有看你的汇编...

用户202137 2010-9-20 09:31

强~~
相关推荐阅读
用户1203741 2012-04-24 12:00
STM32L开发经验之一
STM32L开发经验之一   这2天在调试单位的一个电路板,电路板的核心芯片是ST公司的STM32L152,在进行系统时钟源切换时发现一个问题:当选择系统时钟源为外接振荡器HSE时,有时对...
用户1203741 2011-09-01 22:32
液晶显示器FP71G+不亮的维修
液晶显示器FP71不亮的维修 有一台液晶显示器,型号是FP71G+,开机后不亮。上网查找了一些信息,据说此款显示器出现这种现象多半都是高压有问题,而且还指出了大部分问题都出现那对三极管上(型号为570...
用户1203741 2010-08-19 16:56
STM8的C语言编程(14)-- PWM
                STM8的C语言编程(14)-- PWM 在单片机应用系统中,也常常会用到PWM信号输出,例如电机转速的控制。现在很多高档的单片机也都集成了PWM功能模块,方便用户的应...
用户1203741 2010-08-16 10:02
STM8的C语言编程(13)-- 蜂鸣器
                 STM8的C语言编程(13)-- 蜂鸣器 蜂鸣器是现在单片机应用系统中很常见的,常用于实现报警功能。为此STM8特别集成了蜂鸣器模块,应用起来非常方便。在应用蜂鸣器模...
用户1203741 2010-08-13 09:10
STM8的C语言编程(12)-- AD转换
                                       STM8的C语言编程(12)-- AD转换 在许多的单片机应用系统中,都需要A/D转换器,将模拟量转换成数字量。在STM8...
EE直播间
更多
我要评论
3
11
关闭 站长推荐上一条 /3 下一条