汇编语言的艺术之三:基本认识
四、回路中的指令
回路最重要的是速度,因为本段程式,将在计数器的范围之内,连续执行下去。如果不小心浪费了几个时钟值,在回路的累积下,很可能使程式成为牛步。
要想把回路写好,一定要记清楚每个指令的执行时钟,以便选择效率最高者。同时,要知道哪些指令可以获得相同的处理效果,才能有更多的选择。
其次,在回路中,最忌讳用缓冲器,不仅占用空间大,处理速度慢,而且不能灵活运用,功能有限。另外也应极力避免常数,尽量设法经由暂存器执行,用得巧妙时,常会将整个程式的效率提高百十倍。
还有便是少用 PUSH,POP,DIV,MUL和 CALL 等浪费时钟的指令。除此之外,小心、谨慎,深思、熟虑,才是把回路写好的不二法门。
在前例中,把比较常数的指令换为比较暂存器,便是很好的证明。如果用常数,两段程式决不可能共用,时、空都无谓地浪费了。
以下再举数例,乍看这似乎有些吹毛求疵,但是仔细计算一下所浪费的时间,可能就笑不出声了。
兹假定以下回路需处理五万字元的资料,频率为 10MHZ,其情况为:
1: LOOP1:
2: LODSB
3: XOR AL,[DI]
4: STOSB
5: LOOP LOOP1
本程式计数器等于50,000,每次需
12T+14T+11T+17T=55T 个时钟脉冲
若以50,000次计,需时 47*50,000/10,000,000 秒,即约四分之一秒。
只要稍稍将指令调整一下,为:
1: LOOP1:
2: LODSW
3: XOR AX,[DI]
4: STOSW
5: LOOP LOOP1
这样计数器只要25,000次,每次
16T+18T+15T+17T=66T
则25,000次需时 66*25,000/10,000,000 秒,约六分之一秒,比前面的程式快了二分之一。
同理,在回路中加回路,而每个回路需 17T,也是很大的浪费。倘若加调用 CALL 指令,则需 23T+20T=43T,浪费得更多,读者不可不慎。
当某一段程式用得很频繁时,理应视作子程式,例如下面的 LODAX:
1: LOOP1:
2: CALL LODAX
3: LOOP LOOP1
4: RET
5: LODAX:
6: LODSW
7: XOR AX,[DI]
8: STOSW
9: RET
其实这是贪小失大,仅四个字元的程式,竟用三个字元的调用指令去交换,是绝对得不偿失的。
再如同下面的程式,颇有值得商榷之处。
1: LOOP1:
2: MOV DX,NUMBER1
3: MOV CX,NUMBER2
4: LOOP2:
5: PUSH CX
6: MOV CX,DX
7: LOOP3:
8: LODSW
9: XOR AX,[DI]
10: STOSW
11: LOOP LOOP3
12: INC DI
13: INC DI
14: POP CX
15: LOOP LOOP2
16: RET
第二个回路是多余的,这是高阶语言常用的观念,对组合语言完全不适用。
稍加改动,不损上面程式原有的条件,得到:
1: LOOP1:
2: MOV DX,NUMBER1
3: LOOP2:
4: MOV CX,NUMBER2
5: LOOP3:
6: LODSW
7: XOR AX,[DI]
8: STOSW
9: LOOP LOOP3
10: INC DI
11: INC DI
12: DEC DX
13: JNZ LOOP2
14: RET
这样回路少了一个,程式中将5,6,14,15 各条中原来为15T+2T+12T+17T=46T的时间,省为12,13,14条的2T+16T+17T=35T。