大眾論壇 » 電腦程式設計 » 組合語言的藝術(組合語言的藝術)--基本認識(3)

2006-12-10 01:20 chan0006
組合語言的藝術(組合語言的藝術)--基本認識(3)

[size=2]二、程式要條理通順 [/size]
[size=2][/size]
[size=2]  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之速度最快。[/size]
[size=2] [/size]
[size=2]  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
        .. [/size]
[size=2]  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:
        .. [/size]
[size=2]  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:
        .. [/size]
[size=2]  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 [/size]
[size=2][/size]
[size=2]  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 [/size]
[size=2][/size]
[size=2]  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       ; 還原
        .. [/size]
[size=2]  8,當處理字碼時,需要字母的序數,有這樣的寫法:
        CMP     AL,60H   
        JA      ABCD1  
        SUB     AL,40H      ; 大寫字母
    ABCD:
        ..
    ABCD1:
        SUB     AL,60H      ; 小寫字母
        JMP     ABCD
        要知道字母碼的特色在於大寫為 40H 至4AH,小寫為60H 至6AH ,以上程式,其實只要一個指令就可以了:
        AND     AL,1FH
    簡單明瞭! [/size]
[size=2][/size]
[size=2]  9,大多數的程式在程式師自己測試下很少發生錯誤,而一旦換一另個人執,就會發現錯誤百出。
        其原因在於寫程式者已經假定了正確的情況,當然不會以明知為錯誤的方式操作。可是換了一個人,沒有先入為主的成見,很可能輸入了「不正確」的資料,結果是問題叢生。
        要知道真正的使用者,絕非設計者本人,在操作過程中,按鍵錯誤在所難免。這種錯誤應該在程式中事先加以檢查,凡是輸入資料有「正確、錯誤」之別者,錯誤性資料一定要事先加以排除。
        這樣做看起來似乎程式不夠精簡,可是正確的重要性遠在精簡之上。一旦發生了錯誤,再精簡的程式也沒有使 用價值。
        此外,在程式中常有加、減的運算,這時也應該作正確性檢查,否則會發生上述同樣的問題。[/size]
[size=2] [/size]
[size=2]三、指令應用要靈活 [/size]
[size=2][/size]
[size=2]    有一段很簡單的程式,其寫作的方法甚多,但是指令應用的良窳,會使得程式的效率相去天上地下,難以估計。
    這段程式的用途,是要將一段資料中,英文字元大、小寫相互轉換。當然,轉換的選擇要由使用者決定,在下面程式且略去使用介面,假設已得知轉換的方式。
    設資料在 DS:SI中,資料長度=CX ,大寫轉小寫時BL=0,反之,則BL=1。
    我見過一種寫法,簡直無法原諒:
    1: LOOP1:
    2:        CALL    CHANGE
    3:        JC    LOOP11
    4:        ADD    AL,20H
    5:        JMP    SHORT LOOP12
    6: LOOP11:
    7:        SUB    AL,20H
    8: LOOP12:
    9:        MOV    [SI-1],AL
   10:        LOOP    LOOP1
   11:        RET
   12: CHANGE:
   13:        LODSB
   14:        OR    BL,BL
   15:        JZ    CHANGS
   16:        CMP    AL,61H
   17:        JB    CHARET
   18:        CMP    AL,7AH
   19:        JA    CHARET
   20:        STC
   21: CHARET:
   22:        RET
   23: CHANGS:
   24:        CMP    AL,41H
   25:        JB    CHARET
   26:        CMP    AL,5AH
   27:        JA    CHARET
   28:        CLC
   29:        RET [/size]
[size=2]
    這種程式錯在把由12到29的程式寫得太長,共 25B,有共用的價值,於是作為子程式調用。
    試想一下,每一筆資料,都要調用一次,浪費四個字元事小,但每次要費 23+20個時鐘脈衝,資料多時,不啻為天文數字。更何況這段程式寫得極差,在回路中,又多浪費了幾十個時鐘。關於這一點,下面會繼續討論。
    照上面這段程式,略加改進,寫法如下:
    1: CHANGE:
    2:        LODSB
    3:        OR    BL,BL
    4:        JZ    CHANGS
    5:        CMP    AL,61H
    6:        JB    CHARET
    7:        CMP    AL,7AH
    8:        JA    CHARET
    9:        SUB    AL,20H
   10: CHANG0:
   11:        MOV    [SI-1],AL
   12: CHANG1:
   13:        LOOP    CHANGE
   14:     RET
   15: CHANGS:
   16:        CMP    AL,41H
   17:        JB    CHANG1
   18:        CMP    AL,5AH
   19:        JA    CHANG1
   20:        ADD    AL,20H
   21:        JMP    CHANG1

    這樣的寫法還是不佳,因為在回路中,用常數與暫存器比較,速度較暫存器相比為慢。應該先將需要比較的值,放在暫存器DH,DL 中,改進如次:
    1:        MOV    AH,20H
    2:        MOV    DX,7A61H
    3:        OR    BL,BL
    4:        JZ    CHANGE
    5:        MOV    DX,5A41H
    6: CHANGE:
    7:        LODSB
    8:        CMP    AL,DL
    9:        JB    CHANG1
   10:        CMP    AL,DH
   11:        JA    CHANG1
   12:        XOR    AL,AH
   13:        MOV    [SI-1],AL
   14: CHANG1:
   15:        LOOP    CHANGE
   16:     RET

    以上這段程式,空間小,速度快,每筆資料,平均僅需不到40個時鐘值,以10 MHZ計,十萬筆資料,約需半秒鐘!
請注意程式中所用的技巧,由2至6的分支法,就比下面這種寫法為佳:
    1:        OR    BL,BL
    2:        JZ    CHAN1  
    3:        MOV    DX,5A41H
    4:      JMP    SHORT CHANGE
    5: CHAN1:
    6:        MOV    DX,7A61H
    7: CHANGE:

    這種分支也可以由另一種技巧所取代,即預設法。事先將所需用的參數放在固定的緩衝區中,此時取用即可:
           MOV  DX,BWCOM   ; 比較之預設值  
    這樣程式又簡單些了:
    1:       MOV    AH,20H
    2:        MOV    DX,BWCOM
    3: CHANGE:
    4:        LODSB
    5:        CMP    AL,DL
    6:        JB    CHANG1
    7:        CMP    AL,DH
    8:        JA    CHANG1
    9:        XOR    AL,AH
   10:        MOV    [SI-1],AL
   11: CHANG1:
   12:        LOOP    CHANGE
   13:     RET
[/size]
[size=2]    以上介紹為變數法技巧,即將所要比較的值,放在暫存器中。由於暫存器快速、節省空間,因此程式效率高。更重要的一點,是程式本身的彈性大,只要應用方式統一,事先把參數設妥,即可共用。 [/size]
[size=2]四、回路中的指令 [/size]
[size=2]    回路最重要的是速度,因為本段程式,將在計數器的範圍之內,連續執行下去。如果不小心浪費了幾個時鐘值,在回路的累積下,很可能使程式成為牛步。
    要想把回路寫好,一定要記清楚每個指令的執行時鐘,以便選擇效率最高者。同時,要知道哪些指令可以獲得相同的處理效果,才能有更多的選擇。
    其次,在回路中,最忌諱用緩衝器,不僅佔用空間大,處理速度慢,而且不能靈活運用,功能有限。另外也應極力避免常數,儘量設法經由暫存器執行,用得巧妙時,常會將整個程式的效率提高百十倍。
    還有便是少用 PUSH,POP,DIV,MUL和 CALL 等浪費時鐘的指令。除此之外,小心、謹慎,深思、熟慮,才是把回路寫好的不二法門。
    在前例中,把比較常數的指令換為比較暫存器,便是很好的證明。如果用常數,兩段程式決不可能共用,時、空都無謂地浪費了。 [/size]
[size=2]
    以下再舉數例,乍看這似乎有些吹毛求疵,但是仔細計算一下所浪費的時間,可能就笑不出聲了。
茲假定以下回路需處理五萬字元的資料,頻率為 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。
[/size]
[size=2]            第五節  分支處理 [/size]
[size=2][/size]
[size=2]    比較資料後,作條件分支 (Conditional Jump ),是程式中不可避免的手續。程式一長,分支距離超過 128個字元,條件分支就無法到達。當然,精簡程式有時可以避免這種情形,但卻不儘然。
    處理條件分支的技術很多,其效率端視情況而定。最要緊的是事先規劃,要比較些什麼?在何種情況下?分支到哪里?做些什麼工作等等。
    不僅是寫程式,人的各種能力,都可以由工作的方式判斷出來。智慧高的人,很快就能抓住重點,再分門別類,钜細無遺的理出完整的系統。經過良好訓練的專家,則能根據一套法規,逐步地整理歸納,也能推出合情合理的結果來。[/size]
[size=2]
    老實說,電腦程式的寫作技術還沒有到成熟的階段,當今所有的從業人員,都只能算是「拓荒者」,並沒有真正的「專家學者」。充其量,像我個人一樣,比別人機會好些,天天得以與電腦為伍,多一點經驗而已。
    因此,目前寫程式幾乎可以說沒有可資遵循的法規,海闊天空,愛怎樣寫,就怎樣寫,只要能夠使用,程式賣得出去,賺了大錢,就會被人視為大師。[/size]
[size=2]
    只是這種情況維持不了多久了,初民的壁畫,僅具有歷史意義。今天的程式師,如果不認清現實,立刻覺醒,多致力於法規的制定,電腦將永遠是個不成熟的孩子。一旦這些法規經得住考驗,為未來的專家學者奠定基礎,那才能真正的被視為大師。
    我不諱言我們正朝著這個方向努力,但是,我卻不認為做得到。因為電腦的硬體設計在今後的十年內,必然會有重大的突破,誰都難以預測會有什麼結果。軟體的製作觀念雖然不可能有很大的改變,卻難免會受到影響。只有各位年輕朋友,你們成長在電腦時代,肯多一分耕耘,必有收穫!
    下面,且介紹一些我對條件分支的處理技巧: [/size]
[size=2][/size]
[size=2]一、資料的分類 [/size]
[size=2][/size]
[size=2]  1,位元分類: [/size]
[size=2]
        在本書第四章第五節所舉的,由輸入碼作為輸出字形的處理依據之例,就是採用位元分類的例證。
        但凡以資料位元元作為共同的分類訊息,而且各類皆有獨特的處理方式者,皆應以其位元元為順序,用間接定址或分支技巧,作為程式處理之手段。
[/size]
[size=2]  2,字元分類: [/size]
[size=2]
        每一個字元具有 256種排列組合,設若有 128種以內的分類專案,應該取雙數分類,否則須用連續分類。
        分類之值,立即可以用間接定址執行。但須注意,各分類的入口標題應先行定義。由於定義必須用到雙字元,所以,凡採用連續分類者,其值應乘二。
[/size]
[size=2]  3,間隔分類: [/size]
[size=2]
        在有些情況下,原有資料不容許重新安排,而且其中若干資料已具備分類之特性,這種情況,我們稱之為間隔分類。
        在處理此類資料時,應該先將可以作分類處理的資料提取出來,並視為字串,定義在一緩衝區內。當須要類比時,可利用「比對字串」 (SCAS) 的指令以求得其定義位置,再作間接定址。設有
        4700H,4900H,4F00H,5100H,4A2DH,4EABH
    等鍵盤輸入資料。設上述值在AX中,需要作特殊處理,分別進入COD1至COD6等子程式。
    11將資料定義在緩衝器 ABC中,程式則定義在DEF:
      ABC    DW   4700H,4900H,4F00H,5100H,4A2DH,4E2BH
      DEF    DW   COD1,COD2,COD3,COD4,COD5,COD6
    12使DI=ABC,CX=6:
        MOV     DI,OFFSET ABC
        MOV     CX,6
    13由比對字串後,判斷是否AX中有上述之值,如有,則用間接定址的方式執行之。
        REPNZ   SCASW               ; 比對六組字串
        JCXZ    NOTHING             ; 沒有所比之字串
        SUB     DI,OFFSET ABC+2     ; 得到比對位置值
        CALL    CS:DEF[DI]          ; 或作JMP
          上述之DEF 如果放在DG段中,還可以節省一字元,並可加快速度:
        CALL    DEF[DI]
[/size]
[size=2]二、程式的結構 [/size]
[size=2][/size]
[size=2]    若在程式規劃之初,未先做好準備工作,臨時想用前述的方法,並非絕不可能。但是,東添一點,西補一段,這種程式不僅會導致測試的麻煩,更可能影響未來的維護和調整。
    因此,每當瞭解了工作任務後,需要作間接定址的部份,最好能集中在一個模組內。萬一性質不同必須分割,也應該將間接定址的程式,置放在模組的起頭處。
    這樣做的好處很多,一方面便於擴充功能,每次增加定址因素時,不必在程式中尋來找去,立刻可以安排妥當。其次,這種定址的需求,必然與整體功\能有關,而且定義表相當於一個目錄,把綱領放在前面,按圖索驥,一目瞭然。更重要的,是可以表現出程式結構的層次,層次處理是網狀流程中最難以掌握的一環,不可不慎。
    還有,就是各子程式的標題安排,其位置的先後應以功能的集中性為准。這樣做的好處是,如果有可以共用的程式段,很容易就可合併為一,節省空間。 [/size]
[size=2][/size]
[size=2]三、次序與條件「真」「假」 [/size]
[size=2][/size]
[size=2]    條件分支的「時鐘數」有二個可能,條件符合時,執行分支為 16T,不符合則為 4T ,且繼續下一指令。兩者相差有四倍之多,我們正該利用這一特點,速度重要的條件,都應該設為主流程,否則為分流程。
    尤其是在需要高速的回路中,分支處理得好壞,效率相去甚遠。這種分支需要平時多加小心,培養出良好的習慣。
    CDEF:  
        CMP    AL,'?'
        JZ     ABCD       ; 各比較符號中,'?' 者最少
        LOOP   CDEF       ; NZ條件僅需4T速度較快
    ABCD:  
        .. [/size]

[size=2]四、JMP 與 JMP SHORT [/size]
[size=2][/size]
[size=2]    當程式師專心寫作或偵錯之時,常無法瞻前顧後。然而偵錯完畢程式無誤時,最好徹底檢查一下所有的JMP 指令,經常會大有斬獲!
    因JMP 需要三字元,而JMP SHORT 只要兩個,其條件是所跳越的位址不能超過128 字元。
    在程式編譯時,若向上JMP 的距離在 128字元以內,編譯器會自動譯為兩字元。往下則不然,如在128 字元內,會再多加一個 NOP指令,不僅浪費一字元且多了兩個時鐘。
    因此,細心檢查一下,凡是向下跳,在128 字元以內,皆應改為JMP SHORT 才是。[/size]

頁: [1]


Powered by Discuz! Archiver 5.5.0  © 2001-2006 Comsenz Inc.