回 帖 发 新 帖 刷新版面

主题:[轉貼]程式碼最佳化


                   程式碼最佳化


-----------------------------------
使用 AL/AX 暫存器而不用其它的暫存器
-----------------------------------
    有時使用 AL/AX 暫存器,比起其它的暫存器,可達到更多的最佳化。下面是
一個比較:

             cmp     bx,1234h             ; Compare BX with 1234h (4 bytes)

另一個更好的方式是用:

             cmp     ax,1234h             ; Compare AX with 1234h (3 bytes)

    然而,這只能在 AL/AX 暫存器中「未」存有重要數值時使用。就算你在程式
中多次使用到它,只要你記得先 PUSH 再 POP 回來即可。

----------------------------
使用 DATA 節區而不用其它節區
----------------------------
(註:翻成節區是我的習慣,有人有不同的看法)

    從記憶體中移動一個值至 AX 可以是這樣:

             mov     ax,es:[si]           ; Move ES:[SI] to AX (3 bytes)

另一個更好的方式是用:

             mov     ax,ds:[si]           ; Move DS:[SI] to AX (2 bytes)

----------
清除暫存器
----------
    清除暫存器可以是這樣:

             mov     ax,00h              ; Clear AX (3 bytes)

另一個更好的方式是用:

             sub     ax,ax               ; Clear AX (2 bytes)

另一個同樣好的方式是用:

             xor     ax,ax               ; Clear AX (2 bytes)

--------------
清除 DX 暫存器
--------------
    清除 DX 暫存器可以是這樣:

             mov     dx,00h              ; Clear DX (3 bytes)

或是這樣:

             xor     dx,dx               ; Clear DX (2 bytes)

另一個更好的方式是用:

             cwd                         ; Convert word to doubleword (1 byte)

但這只能用在 AX 暫存器值小於 8000h 時。

--------------------
測試暫存器是否已清除
--------------------
    測試暫存器是否清除可以是這樣:

             cmp     ax,00h              ; AX = 0? (3 bytes)

另一個更好的方式是用:

             or      ax,ax               ; AX = 0? (2 bytes)

-------------------------------------
使用 16 位元暫存器而不用 8 位元暫存器
-------------------------------------
    移動一個數值至一個 16 位元暫存器可以是這樣:

             mov     ah,12h              ; Move 12h to AH (2 bytes)
             mov     al,34h              ; Move 34h to AL (2 bytes)

另一個更好的方式是用:

             mov     ax,1234h             ; Move 1234h to AX (3 bytes)

然而,這只能用在上述兩個 8 位元暫存器是同個 16 位元暫存器的高低位元組時。

-------------------------------------
移動 AL/AX 暫存器至其它的暫存器或反之
-------------------------------------
    移動 AL/AX 暫存器至其它的暫存器可以是這樣:

             mov     bx,ax               ; Move AX to BX (2 bytes)

另一個更好的方式是用:

             xchg    ax,bx               ; Exchange AX with BX (1 byte)

    然而,你必須確定來源暫存器中的值不重要,因為它將保存目的暫存器的值。

----------------------------
使用 DI/SI 為基底索引而非 BP
----------------------------
    從記憶體中移動一個值至 AX 可以是這樣:

             mov     ax,ds:[bp]           ; Move DS:[BP] to AX (3 bytes)

另一個更好的方式是用:

             mov     ax,ds:[si]           ; Move DS:[SI] to AX (2 bytes)

    若 DI/SI 使用很頻繁,你只要記得先 PUSH 再 POP 回來即可。

---------------------------------------------
使用 CMPS, LODS, MOVS, SCAS, STOS 及 REP 指令
---------------------------------------------
    從記憶體中移動一個值至 AX 可以是這樣:

             mov     ax,ds:[si]           ; Move DS:[SI] to AX (2 bytes)

另一個更好的方式是用:

             lodsw                        ; Load AX with DS:[DI] (1 bytes)

    記得先設定或清除方向旗標。有時,更佳是做法是先 PUSH 再 POP 回來。

------------------------
移動一節區之值至另一節區
------------------------
    移動一個節區之值至另一節區,你必須動點手腳,而不能像這樣直接:

             mov     ds,cs               ; Can't do this!

因此,你必須使用一個暫存器做中介:

             mov     ax,cs               ; Move CS to AX (2 bytes)
             mov     ds,ax               ; Move AX to DS (2 bytes)

    但若是 AX 有重要的值,那你必須先 PUSH 再 POP 回來,這樣便增加了 2
Bytes,所以一個更好的方法是用:

             push    cs                  ; Save CS at stack        (1 byte)
             pop     ds                  ; Load DS from stack (CS) (1 byte)

--------------------------------
使用 SHL/SHR 而不用 DIV/MUL 指令
--------------------------------
    以 AL 乘以 2 可以是這樣:

             mov     bh,02h              ; Move 02h to BH (2 bytes)
             mul     bh                  ; Multiply AL with BL (2 bytes)

一個更好的方法是:

             shl     al,01h              ; Multiply AL with 02h (2 bytes)

但這只能用在來源值是 2 的倍數。

----------------------------------
使用目的碼(Object Codes)而不用指令
----------------------------------
    一個遠程呼叫可以是這樣:

             call    far address         ; Make a far call (3 bytes)
address      dd      ?                   ; Address of a procedure (4 bytes)

一個更好的方法是:

callfar      db      9ah                 ; Object code of a far call (1 byte)
address      dd      ?                   ; Address of a procedure (4 bytes)

這只能在目碼之後的值是「字組」或更大時達到最佳化的功能。

--------------
使用procedures
--------------
    假若有些程式碼常被用到,那就可以用副程序來達到最佳化。節省的空間計
算如下:

Bytes saved = (procedure size - 4) * number of invocations - procedure size

  Figure 4 in the parentheses of the formula is there because the size of the
CALL and RET instructions together are 4 bytes.

----------------
讓副程序更有彈性
----------------
    當副程序可以混用時,這可以最佳化你的程式,因為多餘的部分已經消失了:

movefptrend  proc    near                 ; Move file pointer to the end
             mov     al,02h               ;  "        "      "   "   "
movefileptr  proc    near                 ; Move file pointer to end/beginning
             cwd                          ; Convert word to doubleword
movefpointer proc    near                 ; Move file pointer to a offset
             xor     dx,dx                ; Clear DX
             mov     ah,42h               ; Move file pointer
             int     21h                  ; Do it!
             ret                          ; Return!
             endp
             endp
             endp

    你可以由上面的示意計算出節省的空間。

---------------------
使用 DTA 中的所有資訊
---------------------
    DTA (Disk Transfer Area) 是被 INT 21h 的 4eh 及 4fh 號呼叫使用的。
內容如下:

                ----------------------------------------
                Offset Size  Contents
                ----------------------------------------
                  00   Byte  Drive letter
                 01-0B Bytes Search template
                 0C-14 Bytes Reserved
                  15   Byte  File attribute
                 16-17 Word  File time
                 18-19 Word  File date
                 1A-1D DWord File size
                 1E-3A Bytes ASCIIZ filename + extension
                ----------------------------------------

- 若你想重設檔案時間及日期,使用 DTA 比 INT21h's 57h 更好。
- 若你想感染一個檔案,只要將磁碟機代碼換成一個不合法值即可,不用再寫
  多餘的程式在退出部分。換不合法代碼會造成錯誤發生。(然後....我也不知道)

  然而,這也只能在你本來就利用 DTA 時有效。

----------------
最後的忠告及秘訣
----------------
- 清除所有不需的 NOP
- 移動你的程式,看看能否把 JUMP NEAR 換成 JUMP SHORT
- 不要把一次可以得到的值分多次計算
- 用 LEA 而不用 MOV OFFSET
- 使用堆疊存暫時資料一但注意是否為 COM 檔案。
- 使用 CBW 指令清除 AH ,當 AL < 80h 時。
- 使用 DEC/INC 而不用 ADD/SUB 做加/減1
- 使用 DEC/INC 時宜用 16bits 暫存器而非 8bits





--

   Jimmy Chung ( Chung Yuan-Kai )     u801403@Winkie.Oz.nthu.edu.tw
   National Tsing Hua University      bugger@ftp.cis.nctu.edu.tw
         Hsin-Chu, Taiwan             Bugger.bbs@bbs.nsysu.edu.tw

回复列表 (共5个回复)

沙发

晕倒,我从来就只知道前面的那种程序,不知道还有后面的那种奇怪的程序

板凳

有没有更为全面的各指令执行的时钟频率和所占字节的资料?

3 楼

不错,不错,我以前遇到过,但没有想到.谢了.

4 楼

看了受益匪浅呢,谢谢楼主的馈赠

5 楼

顶 ,写的不错,前面的都懂  后面的。。。。呵呵  看来要以后才懂的了了


我来回复

您尚未登录,请登录后再回复。点此登录或注册