四、橋式法:
橋式法是利用讀寫記憶體的特性,將程式中若干指令直接填入,作為臨時便橋,以改變此段程式的功能。
例如在顯示時,希望能提供多種變化,而又不願減低速度及增加太多的程式。最好的方法,便是利用橋式法,在同一位址,填入需要的指令。
橋式法用得好而又靈活時,對程式的效率極有助益。但是應該注意一點,就是只能用在可讀可寫的記憶區中,如若要制成「韌體」,即置入僅讀記憶體(ROM) 中的程式,絕不可使用此法。
下面的實例,即為螢幕顯示的橋式應用。首先,把架橋的「材料」設置在緩沖器中,如:
CDSPMOD DB 88H ;8805 = MOV [DI],AL
DB 30H ;3005 = XOR [DI],AL
DB 08H ;0805 = OR [DI],AL
DB 20H ;2005 = AND [DI],AL
CDSPMOD 即為緩沖器,其中有四個數據,分別為機器碼的相異部份,如分號後所注。因為四組機器碼皆有 05 ,不必再填。=右邊部份,即為該機器語言相對應的指令。
程式部份先設妥功能定義,利用一、所說的變數應用法,依序由0至3先載入暫存器BX中。根據 BX 值,將所需機器碼載入程式中。
10:CLOD:
11: MOV AL,CDSPMOD[BX] ;用BX取預存碼
12: MOV CS:CDSP2[1],AL ;載入CDSP2+1
13:CDSP:
14: SUB SI,SI ;資料由0起
15:CDSP1:
16: LODSB ;取資料
17:CDSP2 LABEL BYTE ;載入的位置
18: XOR ES:[DI],AL ;原碼26 30 05
19: INC DI ;須改 30 部份
20: LOOP CDSP1
21: RET
五、流水線法:
工業上的流水線生產作業,需要極為嚴格的規格限制,原器件分別研制完畢後,統一送到生產線上組裝。
程式亦可采用同樣的方法,只可惜一時手頭上找不到現成的、適用的例子,只得將方法概述如下:
先設定處理流程,凡是能用這種生產流程者,皆適用。
再設定處理流程中所采用的「生產線」,也就是緩沖器。因為流水線上所用的資料都需要由緩沖器提供。
此緩沖器的長度由流程決定,緩沖器中的資料則由各調用本流程的原程式載入。
各調用程式可視個別條件,將所需處理的資料,放在緩沖器內(全部或部份)。待調用後,再從原緩沖器中取出經過處理後的資料。
六、對應表法
凡是指根據某種需要,將經過整理的資料,以某種固定的格式,安排在一特定區域中。每當需要時,立刻可以按照排列的位置取出來使用的,皆可稱之為對應表。
這種對應表是我最喜歡利用的技巧,速度奇快不說,修改也極其容易。尤其是我做事一向不拘小節,寫起程式來,專出小錯。自從采用了表格對照法後,凡是適合這種形式的程式,只要想通了最理想的結構,幾個指令就把程式寫完了。
茲將附錄中所舉的例子,對字形放大所采用的查表法,在此作進一步的介紹。
假設有一組圖形,要在螢幕上左右放大一倍。一般程式師做這種題目,都是在暫存器內移來移去,每一個字元的資料,起碼要移八次之多,每次都要用借位作為轉換值。而轉換時,又要放進一個16位元的暫存器中,盡管可以用回路去做,時間的延誤相當大,讀者可參考附錄二以做比較。
當然,表格要占用空間,以本例而言,如果一次用256B,取足則要512B。
因此這種技術可以說是以空間換取時間。在第一章第三節「效率」的第四條定律下,我們知道鍵盤輸入速度,決定於人的操作速度,而人的反應遠遠不及電腦,故應以人的速度為時間邊際值,盡量設法節省。
目前,所涉及的是顯示時間,每個人在電腦前,都期望著立即得到結果。因此,顯示速度不僅要快,而且越快越好。所以,前述的空時交換應在可能范圍中,視實際的邊際效應,以作取捨。
現在看看資料分析,下面列舉的二進位資料,在左邊為原圖形點陣,在右邊則為放大一倍後的點陣:
原點陣 左右放大一倍
00000001 00000000 00000011
00000010 00000000 00001100
00000011 00000000 00001111
..
01010101 00110011 00110011
..
11111111 11111111 11111111
現在有兩個因素非常明顯,第一,不論什麼點陣,放大後長度加一倍,一字元有256 種。放大後點形種類不變,但字元數加倍為 512個。其次,由於放大後的 512個中,有一半皆相同,故仍可用256 種表示。
至於取前者或後者,當視情況而定。
決定以後,將之定義在緩沖器中,以原圖形的點陣資料作為索引值,即可采間接定址法,立即取得放大後點陣。
在制作對應表時,應養成良好的習慣,根據資料的規則,以等長度、固定的格式輸入。這樣不僅對表中的資料能一目瞭然,而且容易輸入、偵錯、修改,一舉數得。
如某表格為:
100 TBXXX DB 0,1,3,7,0FH,1FH,3FH,7FH,0FFH,2,6,0EH,1EH,3EH,7EH,0FEH
此表看去遠不如下表來得清楚、規律:
100 TBXXX DB 000H,001H,003H,007H,00FH,01FH,03FH,07FH
200 DB 0FFH,002H,006H,00EH,01EH,03EH,07EH,0FEH
從事程式寫作,規律的思考方式及追求,經常事半功倍。這種小技巧看似沒有多大作用。事實上,在輸入時,規則化的結構可以輕易地利用現有的功能,或復制,或修改。更有利的是能一眼看出該表的意義及正確性,在程式偵錯時,往往可以節省大量的時間。
七、模式法
所謂模式法,是指在程式的處理過程中,分析其規律,以期找到一種共同具有的「模式」。並用此模式,設計成為一個個程式單元,以追求最高效率。
這種模式,可用「概念」來代表,但最理想的表達方法,仍以視覺圖形為宜。也就是說,最好能把分析出來的模式,用圖形表示,並據以理解及設計程式。
茲以常用的功能「排序」為例,來說明模式法的應用,並設計成為程式。
先假定需要排序的資料結構為:
11每筆資料之長度固定為一字元。
12資料形式為 ASCII碼,16進位值,由 20H到 7EH。
│? │
模式一供檢查 ├─┤
AL,AH 之大小 ┌ AH <--│? │<-- AL ┐
模 │ ├─┤ │模
模式二交換資料 式 ┤ AL <--│? │<-- AH ├式
其中 AH>AL 一 │ ├─┤ │二
└ SI = │? │ = SI ┘
├─┤
由上圖可見在模式一中,AH為低位資料,AL為高位資料。比較 AL,AH 之大小,即可知是否符合序列規定。如符合,則繼續做下去,否則依模式二,將小值放進低位,大值放進高位住址中。程式只要設法保持此一處理之形式,即可簡單明瞭地完成任務。
1: COMPAR:
2: MOV AH,AL ;設AH為低位值
3: COMPAR1:
4: LODSB ;取資料
5: CMP AL,AH ;比大、小
6: JAE COMPAR ;高位大,不變
7: MOV [SI-2],AX ;交換AH,AL,排序
8: DEC SI ;向低位再查
9: MOV AH,[SI-2]
10: JMP COMPAR1
當然,上面這段程式並不成立,因為沒有出口,永遠做不完。程式的終止有很多方法,一是用計數器,一是用位置來比較,也有用終止指令的,不一而足,各有長短。
首先,假設在DS:SI 中,有一長度值,茲以計數器的回路來試試看:
1: LODSW
2: MOV CX,AX ;似此,3B 18C
;若用 MOV CX,[SI]
; INC SI
; INC SI
;則需 6B,21T
3: SUB AL,AL ;先設最小值,備用
4: COMPAR:
5: MOV AH,AL ;設AH為低位值
6: COMPAR1:
7: LODSB ;取資料
8: CMP AL,AH ;比大、小
9: JB COMPAR2 ;低位大,需排序
10: LOOP COMPAR ;回路
11: RET ;完成
12: COMPAR2:
13: MOV [SI-2],AX ;交換AH,AL,排序
14: DEC SI ;向低位再查
15: MOV AH,[SI-2]
16: JMP COMPAR1
程式中的回路,對前面有一比較分支不太有利,因為回路每次要17T ,比較分支就是現成的回路,不利用形成浪費。
若把回路改為位置比較,程式即為:
1: MOV CX,SI
2: ADD CX,[SI]
3: INC SI
4: INC SI
5: SUB AL,AL ;先設為最小值,備用
6: COMPAR:
7: MOV AH,AL ;設AH為低位值
8: COMPAR1:
9: LODSB ;取資料
10: CMP SI,CX ;比位置到終點?
11: JAE COMRET ;完成
12: CMP AL,AH ;比大、小
13: JAE COMPAR ;高位大,再查
14: MOV [SI-2],AX ;交換AH,AL,排序
15: DEC SI ;向低位再查
16: MOV AH,[SI-2]
17: JMP COMPAR1
18: COMRET:
19: RET
如此,在分支時,在第13條指令做回路,10,11 則比較住址以決定是否完成。這一來,完成結束只有一次,需時 16T,其余所有執行時間皆為4T,較前一回路快了13T 之多。
再試用「終止指令」法,其必要條件為資料中有多余的組合可供選擇。一般多以 00H,0FFH 等極端值比較理想,下面且以0FFH作為終止指令,並置於資料終止處。
1: MOV CL,0FFH ;終止檢查用
2: SUB AL,AL ;先設為最小值,備用
3: COMPAR:
4: MOV AH,AL ;設AH為低位值
5: COMPAR1:
6: LODSB ;取資料
7: CMP AL,CL ;比是否終止指令?
8: JAE COMRET ;完成
9: CMP AL,AH ;比大、小
10: JAE COMPAR ;高位大,再查
11: MOV [SI-2],AX ;交換AH,AL,排序
12: DEC SI ;向低位再查
13: MOV AH,[SI-2]
14: JMP COMPAR1
15: COMRET:
16: RET
似此,程式較短,其他效果差不多。
這段程式,在處理速度上,還大有油水。因為已經檢查過的資料,因為回路關系,還會不斷地重復檢查,是否能夠避免這種情況呢?
事實上,當排序到某住址時,即表示由該住址起,上面已經檢查完畢。因此,只要記錄下來,下次再查時,將住址還原即可。
1: MOV CL,0FFH ;終止檢查用
2: COMPAR0:
3: SUB AL,AL ;先設為最小值,備用
4: COMPAR:
5: MOV AH,AL ;設AH為低位值
6: COMPAR1:
7: LODSB ;取資料
8: CMP AL,CL ;比是否終止指令?
9: JAE COMRET ;完成
10: CMP AL,AH ;比大、小
11: JAE COMPAR ;高位大,再查
12: MOV DI,SI ;暫時保存
13: COMPAR2:
14: MOV [SI-2],AX ;交換AH,AL,排序
15: DEC SI ;向低位再查
16: MOV AH,[SI-2]
真要作大量的資料排序,還有更有效的方法,也是應用模式分析的原則,先找出資料的「型」。
假如以同樣性質的資料為例,為了避免資料一一查找,浪費時間。我們不妨研究一下,是否有可能,直截了當,就把資料依據大小,予以定位,一次排好?
電腦的好處,就在於資料的規律性,我們理應利用這種優點,來找出其排序的模式。
前例曾分為兩個模式,一是查找,一是搬移。如果我們把查找改為記錄,把搬移改為安排,則情形就大大的不同了。
記錄時利用間接定址技巧,每筆取到的資料,皆可視為一個數值,對應於一記錄的緩沖區。如果資料中每筆資料總值大於256 且小於 65536,則可以用二字元記錄之。再若資料為二進位值,可由 0至 255,即設 512個對應單位。
假定原資料在 DS:SI中,長度在CX中。
首先,設一記錄區為:
1: RECORD DB 512 DUP (0)
2: PUSH SI ;程式開始
3: CHECK:
4: LODSB ;取資料,AH永遠為0
5: MOV BX,AX ;利用BX間接定址
6: INC WORD PTR RECORD[BX]
7: LOOP CHECK
8: STORE:
9: MOV SI,OFFSET RECORD+512;指向最
;後記錄
10: MOV BP,OFFSET RECORD ;供檢查
11: POP DI ;資料貯存處
12: STORE1:
13: CMP SI,BP ;查是否完畢?
14: JE RECEND ;完成
15: DEC SI ;向上取
16: DEC SI
17: MOV CX,[SI] ;取記錄值
18: JCXZ STORE1 ;無記錄,重取
19: MOV AX,SI ;當前之位址
20: SUB AX,BP ;差值
21: SHR AX,1 ;原有值
22: STORE2:
23: REP STOSW ;重新載入
24: JMP STORE1 ;繼續
25: RECEND:
26: RET
程式的變化無窮無盡,尤其是用組合語言寫作程式,簡直沒有止境。只要稍稍用點心,加一點點變化因素,一個巧妙無比的程式,就會躍然而出。
寫程式的樂趣,就在於心智的投入。學者們不妨試著把這 式再加以改良,其中還有不少可以下手的地方,養成習慣以後,程式自然就會精簡了。
八、預置法
預置法適用於流程的安排,尤其是在不確定的情況下,有時需要作多項檢查,不僅浪費時間,對空間也不利。
例如有一段程式,其目的在於處理使用者所選擇的流程。由於使用者事先通過介面程式,選妥各項工作,現在必須依某一順序執行。
這是一項難度相當大的工作,要執行固定順序不難,下面的程式就可以達到目的。當然,一如既往,我們會嘗試著將程式一再改進。最後,我們再來討論如何能執行使用者所安排的順序。
設子流程有八種,使用者選用時,可令BX值等於子程式的代號。選用方式為「開關式」,即單數次為開,設定參數,復數次為關,取消設定。
設定後,因為共有八種程式,可以用八個位元來設置所需要執行的旗號。當然,這要看程式的多少而定,八位元正好用一個旗號FLAG:
1: SETUP:
2: CMP BX,MAXVAL ; 最大值檢查
3: JA SETRET ; 超過,無效
4: SHL BX,1 ; 參數乘2
5: JMP SUBTB[BX] ; 各種程式
6: SUBTB DW SUB1 ; 各種程式
7: DW SUB2 ; 程式中設定
8: .. ; flag
9: DW SUBN
10: ENTER:
11: SHR FLAG,1 ; 檢查FLAG
12: JNC ENTER1
13: CALL SUB1 ; 有設定
14: ENTER1:
15: SHR FLAG,1
16: JNC ENTER2
17: CALL SUB2
18: ENTER2:
19: .. ; 如此連續進行八次
顯然這種做法其笨無比,第十條以後,可用回路取代:
10: ENTER:
11: MOV CX,8
12: MOV AL,FLAG ; 暫存器較有效
13: OR AL,AL
14: JZ ENTRET ; 不必做
15: SUB BX,BX
16: LOOP0:
17: SHR AL,1
18: JNC LOOP1
19: PUSH AX
20: PUSH BX
21: PUSH CX
22: CALL SUBTB[BX]
23: POP CX
24: POP BX
25: POP AX
26: LOOP1:
27: INC BX
28: INC BX
29: LOOP LOOP0
30: ENTRET:
31: RET
這樣好得多了,可是,還能不能再加改進呢?組合語言的妙處就在於變化無窮,且看看是否還能變出花樣來。
從設置開始,方式稍微改變一下,旗號的觀念是供程式檢查用。在應用時,要占用一個暫存器,而暫存器有限,浪費了可惜。此外,八個不同的子程式,又要占用一個計數用的暫存器,最好能夠省掉。
因此,設置的重要性就顯而易見了,程式的好壞,並非僅僅在於指令的應用。原始的理念,及程式的規劃,經常在程式設計之前已經決定了。