c51优化设计之循环语句(上)——使用djnz循环指令提高执行效率_第1页
c51优化设计之循环语句(上)——使用djnz循环指令提高执行效率_第2页
c51优化设计之循环语句(上)——使用djnz循环指令提高执行效率_第3页
c51优化设计之循环语句(上)——使用djnz循环指令提高执行效率_第4页
c51优化设计之循环语句(上)——使用djnz循环指令提高执行效率_第5页
已阅读5页,还剩1页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

C51 优化设计之循环语句(上)使用 DJNZ 循环指令提高执行效率 原创 主题词:高效代码;循环语句;Keil C51;DJNZ C51 有三种循环语句即 while,do-while 和 for,这三种循环都可以用来处理同一问 题,基本上三者可以相互替换.但由于 C51 是针对 51 汇编语言的编译器,如果不 注意 51 汇编指令的特点,不同的编程方式可能得到不同的程序性能(执行速度和 代码长度).以计算 1+2+3+.+9+10 为例,下面做一对比. 程序 1: unsigned char i; unsigned char sum; for(i=1,sum=0;i11;i+) sum+=i; 汇编代码为: C:0x0003 7F01 MOV R7,#0x01 C:0x0005 E4 CLR A C:0x0006 FE MOV R6,A C:0x0007 EF MOV A,R7 C:0x0008 2E ADD A,R6 C:0x0009 FE MOV R6,A C:0x000A 0F INC R7 C:0x000B BF0BF9 CJNE R7,#0x0B,C:0007 代码长度(字节):11,执行周期(机器周期):63 程序 2: unsigned char i; unsigned char sum; for(i=10,sum=0;i;i-) sum+=i; 汇编代码为: C:0x000F 7F0A MOV R7,#0x0A C:0x0011 E4 CLR A C:0x0012 FE MOV R6,A C:0x0013 EF MOV A,R7 C:0x0014 2E ADD A,R6 C:0x0015 FE MOV R6,A C:0x0016 DFFB DJNZ R7,C:0013 代码长度(字节):9,执行周期(机器周期):53 程序 3: unsigned char i=11; unsigned char sum=0; while(i-) sum+=i; 汇编代码为: C:0x0003 7F0A MOV R7,#0x0B C:0x0005 E4 CLR A C:0x0006 FE MOV R6,A C:0x0007 AD07 MOV R5,0x07 C:0x0009 1F DEC R7 C:0x000A ED MOV A,R5 C:0x000B 6005 JZ C:0012 C:0x000D EF MOV A,R7 C:0x000E 2E ADD A,R6 C:0x000F FE MOV R6,A C:0x0010 80F5 SJMP C: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-后还有其它操作 ,比如改为: unsigned char i=10,j=0; unsigned 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(unsigned char arg) ACC = arg; / 改写累加器 调用 func 函数参数变量取值都为 0,调用代码为: void test(void) func(0); func(0); func(0); 当将优化级别设置为 8 时(#pragma OPTIMIZE(8),调用 test 函数的汇编代码为: 10: void test(void) 11: 12: func(0); C:0x0003 E4 CLR A C:0x0004 FF MOV R7,A C:0x0005 120011 LCALL func(C:0011) 13: func(0); C:0x0008 120011 LCALL func(C:0011) 14: func(0); 15: 16: C:0x000B 020011 LJMP func(C:0011) 由以上汇编代码可以看出,函数参数变量只赋值一次,参数变量得到了重复利用.但必须注意 的是最后一次调用 func 函数不是 LCALL 指令而是 LJMP 指令,这样优化可以减少一次压栈 和出栈操作,多数情况下以 LJMP 代替 LCALL/RET 不会带来新的问题,但如果 func 函数含 有针对堆栈的操作如操作系统函数,将有可能对程序执行造成灾难性后果,在这种情况下就不 允许这种替换,必须降低优化级别. 当优化级别降低到 7 时,其汇编代码变换为: 10: void test(void) 11: 12: func(0); C:0x0003 E4 CLR A C:0x0004 FF MOV R7,A C:0x0005 120015 LCALL func(C:0015) 13: func(0); C:0x0008 E4 CLR A *垃圾代码 C:0x0009 120015 LCALL func(C:0015) 14: func(0); C:0x000C E4 CLR A *垃圾代码 C:0x000D 120015 LCALL func(C:0015) 15: 16: C:0x0010 22 RET 以上代码虽然解决了替换问题,但带来了两行垃圾代码.函数 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) interrupt 3 P1 = 0x30; 其汇编代码为: C:0x001B 800E SJMP ISR(C:002B) C:0x002B C0E0 PUSH ACC(0xE0) 14: void ISR(void) interrupt 3 15: 16: P1 = 0x30; C:0x002D 759030 MOV P1(0x90),#0x30 17: 18: C:0x0030 D0E0 POP ACC(0xE0) C:0x0032 32 RETI 显然 ISR 只对 P1 口进行了写操作,没有改写其它任何 CPU 寄存器,然而 Keil C51 的确对累 加器 ACC 进行了压栈/ 出栈保护,这保护是不必要的,是一种典型的垃圾代码 . Keil C51 生成这种垃圾代码的原因在于,在.MAP 文件中产生了错误的寄存器使用信息.打开 MAP 文件 ,在文件的最后一部分有描述函数使用寄存器的内容: FUNCTION . . . REG MASK = ISR. . . . . . 0x08000800

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论