好文档就是一把金锄头!
欢迎来到金锄头文库![会员中心]
电子文档交易市场
安卓APP | ios版本
电子文档交易市场
安卓APP | ios版本

C51优化设计之循环语句(上)——使用DJNZ循环指令提高执行效率.doc

6页
  • 卖家[上传人]:公****
  • 文档编号:433198970
  • 上传时间:2024-01-16
  • 文档格式:DOC
  • 文档大小:171.50KB
  • / 6 举报 版权申诉 马上下载
  • 文本预览
  • 下载提示
  • 常见问题
    • C51 优化设计之循环语句(上) 使用 DJNZ 循环指令提高执行效率[原创] 主题词:高效代码;循环语句 ;Keil C51;DJNZC51 有三种循环语句即 while,do-while 和 for, 这三种循环都可以用来处理同一问 题 ,基本上三者可以相互替换 .但由于 C51 是针对 51 汇编语言的编译器 ,如果不注 意 51 汇编指令的特点 , 不同的编程方式可能得到不同的程序性能 ( 执行速度和代 码长度 ).以计算 1+2+3+...+9+10 为例,下面做一对比 .程序 1:unsigned char i; unsigned char sum; for(i=1,sum=0;i<11;i++) {sum+=i;}汇编代码为 :C:0x00037F01MOVR7,#0x01C:0x0005E4CLRAC:0x0006FEMOVR6,AC:0x0007EFMOVA,R7C:0x00082EADDA,R6C:0x0009FEMOVR6,AC:0x000A0FINCR7C:0x000BBF0BF9 CJNE R7,#0x0B,C:0007 代码长度 (字节):11,执行周期 (机器周期 ):63程序 2:unsigned char i; unsigned char sum; for(i=10,sum=0;i;i--) {sum+=i;} 汇编代码为 :C:0x000F7F0AC:0x0011E4C:0x0012FEC:0x0013EFC:0x00142EC:0x0015FEC:0x0016DFFBMOVR7,#0x0ACLRAMOVR6,AMOVA,R7ADDA,R6MOVR6,ADJNZ R7,C:0013C51 优化设计之循环语句(上) 使用 DJNZ 循环指令提高执行效率代码长度 (字节):9,执行周期 (机器周期 ):53程序 3:unsigned char i=11;unsigned char sum=0; while(i--){sum+=i;} 汇编代码为 :C:0x00037F0AMOVR7,#0x0BC:0x0005E4CLRAC:0x0006FEMOVR6,AC:0x0007AD07MOVR5,0x07C:0x00091FDECR7C:0x000AEDMOVA,R5C:0x000B6005JZC:0012C:0x000DEFMOVA,R7C:0x000E2EADDA,R6C:0x000FFEMOVR6,AC:0x001080F5SJMPC:0007代码长度 (字节):15,执行周期 (机器周期 ):130从以上三个不同程序可以看出 ,其运算结果都是 0x37(55), 但最短代码为 9,最长代 码为 15, 最快速度为 53,最慢速度为 130,可见三个程序的性能差异较大 .如何编出占用空间小运行效率高的循环代码呢 ?在 C51 编译环境下要写出优秀 的循环代码必须熟悉 51 汇编语言的指令系统 .观察程序 2,循环控制指令使用了 DJNZ 循环转移指令 ,该指令同时完成计数和循环判断两种操作 ,而且只占用两个 字节,是 51 指令系统中最为高效的循环指令 ,因此在设计循环程序时 ,应尽可能使 C51 将 DJNZ 用于循环程序中 .当然 DJNZ 指令的循环次数是确定的 ,主要用在有 确定循环次数的情况 .DJNZ 指令的一个最大特点是递减计数 ,因此循环程序必须采用递减方式才有可 能编译出 DJNZ 指令 , 如以上程序 2.DJNZ 指令的另一个特点是先减后判断 ,因此 设计循环程序也必须坚持先减后判断的原则 ,否则得不到 DJNZ 指令 ,如以上程序 3.如果将程序 3 改写为 :unsigned char i=10;unsigned char sum=0;while(i){sum+=i;i--;}就可以得到与程序 2 相同的汇编代码 .若 i--后还有其它操作 ,比如改为 :un sig ned char i=10,j=0;un sig ned char sum=0;while(i){sum+=i;i--;j++;}也得不到DJNZ汇编指令,也就是说,循环语句在执行过程中,减1与判断必须是连 续的,且减1在前,判断在后.对于while循环,当将减1与判断合成一步时,应当采 用while(--i).按照以上所述,do-while循环同样可以汇编出DJNZ指令,不再——列 举.但是当循环变量不是通过常数赋值语句完成,而是来自于另一个变量时,for和 while语句无论采用何种控制流程都不能产生 DJNZ指令,因为这两种循环都是先判断后执行的控制逻辑,而DJNZ的执行过程是先执行循环体后进行循环判断.按 照DJNZ的控制流程,只有do-while语句符合这个条件,因此当循环次数不是常量 而是变量时,就必须使用do-while循环语句了 .综上所述,若要使用DJNZ指令提高程序效率,在设计循环程序中应坚持以下三大 原则:① 采用递减计数;② 先减后判断,减与判断连续进行;③ 循环次数为变量时,采用do-while循环.Keil C51垃圾代码之二一一优化级别7与8难以两全导致垃圾代码[原创] 主题词:Keil C51;优化级别(优化等级);垃圾代码[OPTIMIZE注:文中提到的Keil C51版本是8.16.Keil C51编译器优化级别设置越高,编译出的代码就越精炼,执行效率也就越高.C51优化级别 有两个重要特点,一是一个优化级别包含所有比它低的优化级别 ,二是可以对某个函数设置优化级别,但一个函数只能有一个优化级别 ,不允许在函数内部设置优化级别 .优化级别的这两个特点决定了在函数一部分代码需要设置较高级别而另一部分需要设置较低级别的情况下 函数优化级别只能舍高取低,从而导致可能产生的垃圾代码 .下面举一例子说明这一问题 .设在某test函数中调用三次另一 func函数,func函数为一个参数变量,无返回值,定义如下:void func(un sig ned char arg){ACC = arg; //改写累加器}调用func函数参数变量取值都为 0,调用代码为:void test(void){func(0);func(0);func(0);}当将优化级别设置为 8时(#pragma OPTIMIZER)),调用test函数的汇编代码为10: void test(void)11: {12:func(0);C:0x0003E4CLRAC:0x0004FFMOVR7,AC:0x0005120011LCALLfunc(C:0011)13:func(0);C:0x0008120011LCALLfunc(C:0011)14:func(0);15: }16:C:0x000B020011LJMPfunc(C:0011)由以上汇编代码可以看出 ,函数参数变量只赋值一次 ,参数变量得到了重复利用 .但必须注意 的是最后一次调用 func 函数不是 LCALL 指令而是 LJMP 指令 ,这样优化可以减少一次压栈 和出栈操作 ,多数情况下以 LJMP 代替 LCALL/RET 不会带来新的问题 ,但如果 func 函数含有 针对堆栈的操作如操作系统函数 ,将有可能对程序执行造成灾难性后果 ,在这种情况下就不允 许这种替换 ,必须降低优化级别 .当优化级别降低到 7 时,其汇编代码变换为10: void test(void)11: {12:func(0);C:0x0003E4CLR AC:0x0004FFMOV R7,AC:0x0005120015LCALLfunc(C:0015)13:func(0);C:0x0008E4CLR A*** 垃圾代码C:0x0009120015LCALLfunc(C:0015)14:func(0);C:0x000CE4CLR A*** 垃圾代码C:0x000D120015 LCALLfunc(C:0015)15: }16:C:0x001022RET以上代码虽然解决了替换问题 但带来了两行垃圾代码•函数func入口参数为R7,在第一次调 用时已经赋值,而函数执行并不影响 R7,所以以后的调用不必再次赋值 ,函数调用前累加器 A的内容对执行无任何影响,因此清零累加器为典型的垃圾代码 •Keil C51垃圾代码之一 一一中断服务程序ISR中PUSH/POP累加器ACC原创] 主题词:Keil C51;效率;垃圾代码 冲断注:文中提到的Keil C51版本是8.16.在Keil C51手册对中断服务程序函数描述中有这样一句话 :When required, the contents of ACC, B, DPH, DPL, and PSW are saved on the stack at function invocation time.话的意思是说,如果中断服务程序(以下简称ISR)执行过程中改写了 CPU寄存器中的内容,那 么Keil C51在中断服务程序开始处会自动添加寄存器压栈指令 ,言外之意是说,如果ISR没有对某个寄存器造成破坏,Keil C51不会对该寄存器保护•这么说Keil C51还是相当智能的,但事 实并非如此,比如下面这个中断函数:void ISR(void) in terrupt 3{P1 = 0x30;}其汇编代码为:C:0x001B800E SJMPISR(C:002B)C:0x002BC0E0 PUSHACC(0xE0)14: void ISR(void) in terrupt 315: {16:P1 = 0x30;C:0x002D759030 MOVP1(0x90),#0x3017: }18:C:0x0030D0E0 POPACC(0xE0)C:0x003232 RETI显然ISR只对P1 口进行了写操作,没有改写其它任何 CPU寄存器,然而Keil C51的确对累加 器ACC进行了压栈/出栈保护,这保护是不必要的,是一种典型的垃圾代码•Keil C51生成这种垃圾代码的原因在于 ,在 .MAP文件中产生了错误的寄存器使用信息 •打开MAP文件,在文件的最后一部分有描述函数使用寄存器的内容 :FUNCTION . . . REG MASKISR @0x08000800main @0x1e000cfb第11位(1000 0000 0000,0x800)恰好对应累加器 ACC,该位为1说明函数改写了该寄存器,所 以Keil C51才又加了两行压栈/出。

      点击阅读更多内容
      关于金锄头网 - 版权申诉 - 免责声明 - 诚邀英才 - 联系我们
      手机版 | 川公网安备 51140202000112号 | 经营许可证(蜀ICP备13022795号)
      ©2008-2016 by Sichuan Goldhoe Inc. All Rights Reserved.