热度 14|||
#include uint8_t while_inc(uint8_t ch) { uint8_t i = 0;
do { ch ++; }while (++i < 8); return ch; } | #include "stdint.h" uint8_t while_dec(uint8_t ch) { uint8_t i = 0;/*不产生汇编代码*/ i = 8; do { ch ++; } while (--i); return ch; } |
代码-1 用“++”判断的while代码段 | 代码-2 用“--”判断的while代码段 |
上面两段示例代码中,循环次数都一样,但是在有些平台中,时空效率却大不相同。以51架构单片机为例,我们来分析它编译出来的汇编代码。
; FUNCTION _while_inc ;---- Variable 'ch' assigned to Register 'R7' ---- /* 用R7表示形参ch */ ;---- Variable 'i' assigned to Register 'R6' ----/*用R6表示局部变量i*/ 0000 E4 CLR A /* i = 0; */ 0001 FE MOV R6,A 0002 ?C0003: 0002 0F INC R7 /* ch++ */ ; 代码“while (++i < 8)”对应汇编语句 0003 0E INC R6 0004 EE MOV A, R6 0005 C3 CLR C 0006 9408 SUBB A, #08H 0008 40F8 JC ?C0003 000A ?C0004: 000A 22 RET |
代码-3 C51平台“++”编译得到的汇编代码段(C51平台) |
; FUNCTION _while_dec ;---- /* 用R7表示形参ch 用R6表示局部变量i */ 0000 E4 CLR A /* uint8_t i = 0 令人困惑?? */ 0001 7E08 MOV R6,#08H /* i = 8; */ 0003 ?C0003: 0003 0F INC R7 /* ch++ */ ; 代码“while (--i)”对应汇编语句 0004 DEFD DJNZ R6,?C0003 0006 ?C0004: 0006 22 RET |
代码-4 C51平台“--”编译得到的汇编代码段(C51平台) |
从上述的汇编代码中,我们发现用“++”和“--”的两段while循环代码,在空间上前者比后者多了4条指令共5个字节,时间上多花费了8次*4=32个指令周期,如果循环次数进一步加大,这一差距就不能忽视。
为何会有如此之多的差异?其本质原因是因为C51拥有一条“复合指令DJNZ”,其定义如下,意为:将“寄存器或者某个ram内的byte变量”的值减1,完事后判断不为0则跳转”。该指令融合了“算术运算 + 逻辑运算 + 跳转”三个功能。
所以,你该知道,大牛的代码while用"--和0比较",一般的代码就是“++和var比较”或者用“for循环”。
一般来说,不管什么平台,判断0总是效率较高的,毕竟cpu的状态寄存器都有个Zero状态位。
------------------------------------------------------------------------------------------------------------------------------------------------
而对于M3平台,两段代码并没有带来明显的时空差异。
while_inc PROC MOVS r1,#0 |L1.2| ADDS r0,r0,#1 ADDS r1,r1,#1 UXTB r1,r1 UXTB r0,r0 CMP r1,#8 BCC |L1.2| BX lr ENDP | while_dec PROC MOVS r1,#8 |L1.2| ADDS r0,r0,#1 SUBS r1,r1,#1 UXTB r0,r0 ANDS r1,r1,#0xff BNE |L1.2| BX lr ENDP |
M3平台“++”的汇编代码段(M3平台) | M3平台“--”的汇编代码段(M3平台) |
这两段汇编,前者之所以比后者多了一句“UXTB r1,r1”,因为加法的值需要32位和8位的数值转换,归零的减法不需要。这又应那个原则:“零值”操作效率较高。
因此,一个软件工程师,需要对其操作平台的指令系统有足够的熟悉,对基本C语句的汇编转换了然于心,才能写出高质、高效的代码。老工程师的价值,也体现在此。
另外值得一提的是,在这个实验中我发现一个比较有意思的现象:在函数while_dec中变量声明兼初始化代码“uint8_t i = 0;”,紧随其后就是“i = 8”的覆盖赋值代码。而它在C51平台编译器上,却产生了一个冗余代码“CLR A”,令人困惑。在M3平台的编译中,就没有这样的现象了。不管怎样,随着编译器越来越智能,变量声明时即刻初始化,是一个非常好的编程习惯,它不会对代码空间增加额外的负担。
cauhorse: 很多人也不知道while(--i)和while(i--)的差异。
cauhorse: 其实主要也是对应汇编代码的区别,原来做“软件延时”的时候试过,果然还是“--i”最省。
sedatefire: 嗯 哈 我都没想到这一层 我比较少用这个硬延时(初始化用用)
我遇到更邪门的事儿呢
同样的while (i--) 放在不同的位置,发现耗时不同,后来发现是指令集对齐的 ...
cauhorse: 我8位机用的最多,可能没遇到过对齐问题,不过编译器有时会闹点鬼,估计还是自己学艺不精。
sedatefire: STM32有个 fetch预取指的东西 涉及到指令周期 和8byte对齐有关
是指令周期影响了延时精度 深究原理的话 和体系结构有关 ...
cauhorse: 这个能通过预编译语句强制对齐的不?