24小时热门版块排行榜    

CyRhmU.jpeg
查看: 729  |  回复: 10
当前主题已经存档。
当前只显示满足指定条件的回帖,点击这里查看本话题的所有回帖

sdlj8051

金虫 (著名写手)

[交流] [转贴]汇编艺术

汇编语言可以说是未经整理的、原始的计算机语言,读者们大可下一番功夫,找出
其应用的规则,以发挥最高的效率。在下面,我仅就个人的经验,提供一些浅见,以供
切磋研讨。
    要写好程序,首先应熟记8088指令的时钟脉冲(Clock )及指令长度,一般汇编语
言手册中,都详列了与各指令相关的数据。「工欲善其事,必先利其器」,此之谓也。
    本节所讨论的,是一般程序员容易忽略的细节,所有的例子都是从我所看过的一些
程序中摘录下来的。看来没什么大了不起,可是程序的效率,受到这些小地方的影响很
大。更重要的是,任何一个人,只要有「小事不做,小善不为」的习惯,我敢断言,这
个人不会有什么大成就!
    我最近才查到 Effective Address (EA) 的时钟值,我觉得没有必要死记。原则上,
以寄存器为变量,做间接寻址时为5个时钟,用直接寻址则为6个;若用了两组变量,
则为7至9个,三组则为11或12个。
    为了便于叙述,下面以"T"表「时钟脉冲」; "B"表字符。其中
    时钟脉冲T = 1 / 振荡频率

一、避免浪费速度及空间

    汇编语言的效率建立在指令的运用上,如果不用心体会下列指令的有效用法,汇编
语言的优点就难以发挥。
  1,    CALL    ABCD
        RET
    这种写法,是没有用心的结果,共享了 4B,23T+20T,完全相同的功能,如:
        JMP     ABCD  或
        JMP     SHORT ABCD
    却只要 2-3B,15T。
        此外,上述的CALL XXXX 是调用子程序的格式,在直觉认知上,与JMP XXXX完
全不同。对整体设计而言,是不可原谅的错误,侦错的时候,也很难掌握全盘的理念。
        尤其是在精简程序的时候,很可能会遇到 ABCD 这个子程序完全独立,是则把
这段程序直接移到 ABCD 前,不仅能节省空间,而且使程序具有连贯性,易读易用。

  2,    MOV     AX,0
    同样,这条指令要 3B,4T,如果用:
        SUB     AX,AX 或
        XOR     AX,AX
    只要 2B,3T, 唯一要注意的是,后者会影响旗号,所以不要用在有旗号判断的指
令前面。
        在程序写作中,经常需要将寄存器或缓冲器清为0,有效的方法,是使某寄存
器保持为0,以便随时应用。
        因为,MOV [暂存器],[暂存器] 只要 2B,2T, 即使是清缓冲器,也比直接填
0为佳。
        只是,如何令寄存器保持0,则要下一番功夫了。
        还有一种情况,就是在一回路中,每次都需要将 AH 清0,此时对速度要求很
严,有一个指令 CBW 原为将一 个字符转换为双字符,只需 1B,2T 最有效率。可是应
该注意,此时 AL 必须小于 80H,否则 AH 将成为负数。
  3,    ADD     AX,AX
    需要 2B,3T不如用:
        SHL     AX,1
    只要2B,2T。

  4,    MOV     AX,4
    除非这时 AH 必为0,否则,应该用:
        MOV     AL,4
    这样会少一个字符。

  5,    MOV     AL,46H
        MOV     AH,0FFH
    为什么不写成:
        MOV     AX,0FF46H
    不仅省了一个字符,四个时钟,而且少打几个字母!

  6,    CMP     CX,0
    需要 4B,4T, 但若用:
     OR      CX,CX
    完全相同的功能,但只要 2B,3T。再若用:
        JCXZ    XXXX
    则一条指令可以替代两条,时空都省。不幸这条指令限用于CX ,对其他暂器无效。

  7,    SUB     BX,1
    这更不能原谅,4B,4T无端浪费。
        DEC     BX
    现成的指令,1B,2T为何不用?
        如果是
SUB     BL,1
也应该考虑此时 BH 的情况,若可以用
  DEC     BX
取代,且不影响后果,亦不妨用之。

  8,    MOV     AX,[SI]
        INC     SI
        INC     SI
    这该挨骂了,一定是没有记熟指令,全部共4B,21T。
        LODSW
    正是为这个目的设计,却只要 1B,16T。

  9,    MOV     CX,8
        MUL     CX
        写这段程序之时应先养成习惯,每遇到乘、除法,就该打一下算盘。因为它们
太浪费时间。8位的要七十多个时钟,16位则要一百多。所以若有可能,尽量设法用简
单的指令取代。
        SHL     AX,1
        SHL     AX,1
        SHL     AX,1
     原来要 5B,137T,现在只要 6B,6T。如果CX能够动用的话,则写成:
   MOV     CL,3
   SHL     AX,CL
     这样更佳,而且CL之值越大越有利。用CL作为计数专 用暂存器,不仅节省空间,
且因指令系在 CPU中执行,速 度也快。
        可是究竟快了多少? 我们做了些测试,以 SHL为例,在10MHZ 频率的机器上,
作了3072 ×14270次,所测得时间为:
    指  令 :SHL   AX,CL         SHL   AX,n
          CL = 0 , 23 秒     n = 0 , 无效
   CL = 1 , 27 秒     n = 1 , 14 秒
          CL = 2 , 32 秒     n = 2 , 28 秒
          CL = 3 , 36 秒     n = 3 , 42 秒
          CL = 4 , 40 秒     n = 4 , 56 秒
          CL = 5 , 44 秒     n = 5 , 71 秒
          CL = 6 , 49 秒     n = 6 , 85 秒
          CL = 7 , 54 秒     n = 7 , 99 秒
        由此可知,用CL在大于2时即较分别执行有效。
        此外,亦可利用回路做加减法,但要算算值不值得,且应注意是否有调整余数
的需要。

10,    MOV     WORD PTR BUF1,0
        MOV     WORD PTR BUF2,0
        MOV     WORD PTR BUF3,0
        MOV     BYTE PTR BUF4,0
        ..
        我见过太多这种程序,一见就无名火起! 在程序中,最好经常保留一个寄存器
为0,以便应付这种情况。即使没有,也要设法使一寄存器为0,以节省时、空。
        SUB     AX,AX
        MOV     BUF1,AX
        MOV     BUF2,AX
        MOV     BUF3,AX
        MOV     BUF4,AL

     14B,59T取代了 24B,76T,当然值得。只是,还是不 如事先有组织,考虑清楚各
个缓冲器间的应用关系。以前面举的例来说,假定各缓冲器内数字,即为其实际位置关
系,则可以写成:
      MOV     CX,3
  如已知 CH 为0,则用:
MOV     CL,3
        SUB     AX,AX
        MOV     DI,OFFSET BUF1
        REP     STOSW
        STOSB
    这段程序越长越占便宜,现在用10B,37T,一样划算。

11,子程序之连续调用:
        CALL    ABCD
        CALL    EFGH
        如果 ABCD,EFGH 都是子程序,且调用的次数甚多,则上述调用的方式就有待
商榷了。因为连续两次调用,不仅时间上不划算,空间也浪费。
        若ABCD一定与EFGH连用,应将ABCD放在EFGH之前:
        ABCD:
            ..
        EFGH:
            ..
        像这样,只要调用ABCD就够了,但这种情形多半是程序员的疏忽所致,如两个
子程序必需独立使用,而上述连续调用的机会超过两次以上,则应该改为:
        CALL    ABCDEF
        而ABCDEF则应为:
        ABCDEF:
               CALL    ABCD
        EFGH:
            ..
        这样的写法速度不会变慢,而空间的节省则与调用的次数成正比。

12,常有些程序,当从缓冲器中取数据时,必须将寄存器高位置为0。如:
        SUB     AH,AH
        MOV     AL,BUFFER
     这时应该将 BUFFER 先设为:
        BUFFER  DB  ?,0
     然后用:
        MOV     AX,WORD PTR BUFFER
        如此,不但速度快了,空间也省了。

13,有时看来多了一个指令,但因为指令的特性,反而更为精简。如:
OR ES:[DI],BH
OR ES:[DI+1],BL
    这样需要8B,32T,如果改用下面的指令:
XCHG BL,BH
OR ES:[DI],BX
XCHG BH,BL
    则需7B,28T。

14,PUSH  及 POP  是保存寄存器原值的指令,都只需一个字符,但却很费时间。
        PUSH  占 15T,POP 占12T,除非不得已,不可随便使用。有时由于子程序说明
不清楚,程序员为了安全,又懒得检查,便把寄存器统统堆在堆栈上。尤其是在系统程
序或子程序中,经常有到堆栈上堆、取的动作。实际上,花点功夫,把寄存器应用查清
楚,就可以增进不少效率。
        要知道,系统程序及某些子程序常常应用,有关速度的效率甚大,如果掉以轻
心,就是不负责任!
        保存原值的方法很多,其中较有效率的是放到一些不用的寄存器里。以我的经
验,堆栈器用途最少,正好用作临时仓库。但最好的办法,还是把程序中寄存器的应用
安排得合情合理,不要浪费,以免堆得太多。
        还有一种方法,是在该子程序中,不用堆栈的手续,但另设一个入口,先将寄
存器堆起,再来调用不用堆栈的子程序。这两个不同的入口,可以分别提供给希望快速
处理,或需要保留寄存器原值者调用。
        当然,更简单有效的方法,则是说明本段程序中某些寄存器将被破坏,而由调
用者自行保存之。

二、程序要条理通顺

  1,在比较判断的过程中,邻近值不必连比。
        CMP     AL,0
        JE      ABCD0
        CMP     AL,1
        JE      ABCD1
        CMP     AL,2
        JE      ABCD2
        ..
    应为:
        CMP     AL,1
        JNE     ABCD0
    ABCD1:
        ..
    在标题为ABCD0 中,再作:
        JA      ABCD2
    这种做法端视时间效益而定,似此 ABCD1之速度最快。

  2,未经慎思的流程:
        ADD     AX,4
    ABCD:
        STOSW
        ADD     AX,4
        ADD     DI,2
        LOOP    ABCD
        ..
    稍稍动点脑筋,就好得多了:
    ABCD:
        ADD     AX,4
        STOSW
        INC     DI
        INC     DI
        LOOP    ABCD
        ..

  3,错误的处理方式:
        MOV     BX,SI
    ABCD:
        MOV     BX,[BX]
        OR      BX,BX
        JZ      ABCD1
        MOV     SI,BX
        JMP     ABCD
    ABCD1:
        LODSW
        ..
    上例应该写成:
        MOV     BX,SI
    ABCD:
        LODSW
        OR      AX,AX
        JZ      ABCD1
        MOV     SI,BX
        JMP     ABCD
    ABCD1:
        ..

  4,错误的流程:
        TEST    AL,20H
        JNZ     ABCD
        CALL    CDEF[BX]
        JMP     SHORT ABCD1
    ABCD:
        CALL    CDEF[BX+2]
    ABCD1:
        ..
应该写成:
        TEST    AL,20H
        JZ      ABCD
        INC     BX
        INC     BX
    ABCD:
        CALL    CDEF[BX]
    ABCD1:
        ..

  5,下面是时间的损失:
        PUSH    DI
        MOV     CX,BX
        REP     STOSB
        POP     DI
        PUSH,POP 很费时间,应为:
        MOV     CX,BX
        REP     STOSB
        SUB     DI,BX
        同理,很多时候稍稍想一下,就可省下一些指令:
        PUSH    CX
        REP     MOVSB
        POP     CX
        SUB     DX,CX
    为什么不干脆些?
        SUB     DX,CX
        REP     MOVSB

  6,有段程序,很有规律,但却极无效率:
    X1:
        TEST    AH,1
        JZ      X2
        MOV     BUF1,BL
    X2:
        TEST    AH,2
        JZ      X3
        MOV     BUF2,DX     ; 凡双数用DX,单数用BL
    X3:
        TEST    AH,4
        JZ      X4
        MOV     BUF3,BL
    X4:
        ..                  ; 以下各段与上述程序相似
    X8:
        ..
        这种金玉其表的程序,最没有实用价值,改的方法应由缓冲器着手,先安排成
序列,由小而大如:
        BUF1    DB  ?
        BUF2    DW  ?
        BUF3    DB  ?
        BUF4    DW  ?
        ..
    然后,程序改为:
        MOV     DI,OFFSET BUF1      ; 第一个缓冲器
        MOV     AL,BL
        MOV     CX,4
    X1:
        SHR     AH,1
        JZ      X2
        STOSB
    X2:
        SHR     AH,1
        JZ      X3
        MOV     [DI],DX
        INC     DI
        INC     DI
    X3:
        LOOP    X1

  7,回路最怕千回百转,不畅不顺,如:
        SUB     AH,AH
    ABCD:
        CMP     AL,BL
        JB      ABCD1
        SUB     AL,BL
        INC     AH
        JMP     ABCD
    ABCD1:
        ..
      以上 ABCD1这个入口是多余的,下面就好得多:
        MOV     AH,-1
    ABCD:
        INC     AH
        SUB     AL,BL
        JA      ABCD
        ADD     AL,BL       ; 还原
        ..

[ Last edited by sdlj8051 on 2007-2-10 at 10:01 ]
回复此楼
已阅   回复此楼   关注TA 给TA发消息 送TA红花 TA的回帖

sirenshen

木虫 (正式写手)

0.5

难得还有用汇编的
5楼2006-11-17 15:06:47
已阅   回复此楼   关注TA 给TA发消息 送TA红花 TA的回帖
相关版块跳转 我要订阅楼主 sdlj8051 的主题更新
普通表情 高级回复(可上传附件)
信息提示
请填处理意见