程式碼最佳化(翻譯文章)


-----------------------------------

使用 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

 

 

----------------

讓副程序更有彈性

----------------

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

 

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 檔案名稱[DOS8.3](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