列举一些如何在8051单片机上写出高性能代码的方法,高性能指的是编译出的代码具有更小的size和更快的执行速度。以下的方法在大多数情况下都是能够起作用的。
- 存储模式(Memory Model)
存储模式最能够影响最终产生代码的大小和执行速度。编译时采用SMALL模式可以产生最小、最快的代码。在SMALL模式下所有的变量,除非特别说明,都会存放在8051内部存储区。单片机访问内部存储区的速度非常看(通常是1~2个时钟周期),产生的代码尺寸也比使用COMPACT或LARGE模式产生的代码小很多。比如以下代码
void do_nothing(void);
void main()
{
unsigned char i;
for (i=0; i<100; i++)
{
do_nothing();
}
}
void do_nothing(void)
{
;
}
若使用SMALL模式进行编译,生成的汇编代码如下:
; FUNCTION main (BEGIN)
; SOURCE LINE # 3
; SOURCE LINE # 4
; SOURCE LINE # 6
0000 E4 CLR A
0001 F500 R MOV i,A
0003 ?C0001:
; SOURCE LINE # 7
; SOURCE LINE # 8
0003 120000 R LCALL do_nothing
; SOURCE LINE # 9
0006 0500 R INC i
0008 E500 R MOV A,i
000A C3 CLR C
000B 9464 SUBB A,#064H
000D 40F4 JC ?C0001
; SOURCE LINE # 10
000F ?C0004:
000F 22 RET
; FUNCTION main (END)
; FUNCTION do_nothing (BEGIN)
; SOURCE LINE # 12
; SOURCE LINE # 13
; SOURCE LINE # 15
0000 22 RET
; FUNCTION do_nothing (END)
在SMALL模式中,变量i保存在内部存储区。访问i的指令MOV A, i 和 INC i 只需要两个字节的空间。另外,执行这些指令只需要一个时钟周期。生成的代码size总共为17bytes。
若使用LARGE模式,则生成下面的汇编指令
; FUNCTION main (BEGIN)
; SOURCE LINE # 3
; SOURCE LINE # 4
; SOURCE LINE # 6
0000 E4 CLR A
0001 900000 R MOV DPTR,#i
0004 F0 MOVX @DPTR,A
0005 ?C0001:
; SOURCE LINE # 7
; SOURCE LINE # 8
0005 120000 R LCALL do_nothing
; SOURCE LINE # 9
0008 900000 R MOV DPTR,#i
000B E0 MOVX A,@DPTR
000C 04 INC A
000D F0 MOVX @DPTR,A
000E E0 MOVX A,@DPTR
000F C3 CLR C
0010 9464 SUBB A,#064H
0012 40F1 JC ?C0001
; SOURCE LINE # 10
0014 ?C0004:
0014 22 RET
; FUNCTION main (END)
; FUNCTION do_nothing (BEGIN)
; SOURCE LINE # 12
; SOURCE LINE # 13
; SOURCE LINE # 15
0000 22 RET
; FUNCTION do_nothing (END)
在LARGE模式中,变量i存放在外部存储区。为了访问变量i,编译器需要先加载该变量的地址,然后执行外部数据访问操作(offset为0001h~0004h), 执行这两条执行需要花费4个时钟周期。0008h~000dh是i++的指令。这些指令占用了6bytes内存空间,执行时间是7个时钟周期。在LARGE模式下,生成的代码大小总共为22bytes。
2. 变量存储方式
由于CPU访问内部存储区比外部存储区效率更高,所以需要频繁擦写的变量需要存放在内部存储区更好一些。内部存储区包括register, bit,stack以及存储类型为data的变量。
由于8051的内部存储区有限(128字节或256字节),所以程序中使用的变量不一定都能放到内部存储区中。此时你需要把一些变量分配到其他存储区。这里介绍两种方法。
2.1 在keil中设置memory model,让编译器去做这个工作。这个方法最简单,但生成的代码的不是最优的。
2.2 使用xdata存储类型声明将一些不经常使用的变量放置到外部存储区,而频繁擦写的变量放置到内部存储区。这样就能够合理的使用单片机的资源,生成最优的代码。
3. 定义变量的类型
8051家族都是8位单片机。使用8位的数据类型(char/unsigned char)会比使用 int/long类型的数据参与运算更有效率。基于此,我们在代码中尽量使用8位的数据类型。
4. 无符号类型
如果使用有符号类型变量,编译器会产生跟多的代码,所以尽量使用无符号类型变量。
5. 局部变量
在做代码优化时,编译器会尝试将局部变量放置到寄存器中,寄存器访问是最快的内存访问方式,所以我们尽可能使用局部变量。
如将变量i改为signed char类型
1 void do_nothing(void);
2
3 void main()
4 {
5 1 signed char i;
6 1 for (i=0; i<100; i++)
7 1 {
8 2 do_nothing();
9 2 }
10 1 }
11
12 void do_nothing(void)
13 {
14 1 ;
15 1 }
生成的汇编代码为
; FUNCTION main (BEGIN)
; SOURCE LINE # 3
; SOURCE LINE # 4
; SOURCE LINE # 6
0000 E4 CLR A
0001 F500 R MOV i,A
0003 ?C0001:
; SOURCE LINE # 7
; SOURCE LINE # 8
0003 120000 R LCALL do_nothing
; SOURCE LINE # 9
0006 0500 R INC i
0008 C3 CLR C
0009 E500 R MOV A,i
000B 6480 XRL A,#080H
000D 94E4 SUBB A,#0E4H
000F 40F2 JC ?C0001
; SOURCE LINE # 10
0011 ?C0004:
0011 22 RET
; FUNCTION main (END)
; FUNCTION do_nothing (BEGIN)
; SOURCE LINE # 12
; SOURCE LINE # 13
; SOURCE LINE # 15
0000 22 RET
; FUNCTION do_nothing (END)
通过比较,可以看出,i改为signed char类型后,汇编中多了一条指令
000B 6480 XRL A,#080H
致使最终生成的代码size为19bytes,大于i为unsigned char的情况。
6. 算法的影响
有些时候,更好的算法也会提高代码的速度,减小代码的存储空间。
以上参考自keil编译手册。