初級優化篇
說到優化,很多人又不屑一顧了,「現在電腦速度都那麼快了,再快那麼百分之幾有什麼意義啊」。這麼說確實有些道理,現在的編譯器編譯後的結果已經是充分優化過了,除了圖形圖像多媒體等特定軟體的開發外、多數情況下刻意的優化確實沒必要,但是如果開發人員在編寫代碼的時候已經具有了優化意識,在完成優化的同時,又能保證了甚至提升開發效率,何樂而不為呢?
當然,演算法的設計都是最佳化的核心,絕大多數情況下,程式的執行效率高低主要由開發人員對程式整體掌握,演算法的設計等來決定!但有時候針對細節的最佳化也是有一定意義的!
而這種優化在許多情況下也不需要直接透過彙編來寫程式碼實現,但這種情況下卻也能體現出掌握彙編知識的優越性!
如下面兩個函數:
function GetBit(i: Cardinal; n: Cardinal): Boolean;
begin
Result := Boolean((i shr n) 和 1);
end;
function GetBit(i: Cardinal; n: Cardinal): Boolean;
begin
Result := Boolean((1 shl n) and i);
end;
對應的彙編程式碼:
MOV ECX, EDX
SHR EAX, CL
AND EAX, $01
MOV ECX, EDX
MOV EDX, $01
SHL EDX, CL
AND EAX, EDX
它們的作用一樣,都是取i某位的值,為1返回True,0返回False!
表面上看可能都會認為兩個函數的執行效率一樣,實際上還是有區別的,第一段程式是的移位操作是對i進行的,按照Delphi中預設的呼叫約定register,此時的i的值是存在暫存器EAX中,移位操作可直接完成;而第二段程式則不同,要對立即數1完成移位操作,必須先將其傳送到暫存器,由此也就必然多出一條指令!當然也不是所有情況下,指令少就一定比指令多要快,具體執行時還要考慮指令執行的時鐘週期和指令的配對等問題(後面再介紹些),獨立出來也說明不了問題,只有在具體程式碼環境中才好作比較。
一般情況下這種效率上的執行差異實在是太微不足道了,但在程式設計期間時刻保持著優化的意識絕不是件壞事!如果這類程式碼位於循環的最裡層,N個時脈週期經過大量循環的累積,產生的執行效率差異也可能變的很大!
上面只是個很小的例子,由此可以看出在開發中如果能站在彙編的角度思考一些問題,能在保證開發效率的同時用高級語言編寫出更有效率的細節代碼!但還有很多時候,細節優化還要用使用嵌入彙編程式碼來完成,而且有些時候由於嵌入彙編程式碼應用,還能讓程式碼編寫變得更有效率。
如需要將一個32位數的位元組順序顛倒,在Delphi中,完全用高階語言實作怎麼做?用移位可以,多次呼叫內建函數Swap也可以,但如果想到一條BSWAP指令,這一切變得很簡單。
function SwapLong(Value: Cardinal): Cardinal;
asm
BSWAP EAX
end;
註:同上,Value的值是存在暫存器EAX中,而32位數的值也透過EAX返回,所以只需要一句即可。
當然多數的嵌入彙編優化沒有這麼簡單,不過透過大學裡所學的那一點點彙編知識也很難做到更深入的優化,也只能透過不斷的積累,對比編譯後的彙編程式碼獲取經驗!好在多數情況下,細節優化並不是程式設計的主體。
但如果所開發的程式涉及圖形圖像多媒體等方面,還是有必要進行更深入的優化!還好不管是浮點指令的最佳化或是應用MMX、SSE、3DNow等完成優化,Delphi6都能提供良好的支援。即使是想早期版本的Delphi支援這些CPU擴充指令集或是想要支援以後新的CPU指令集,利用Delphi在嵌入彙編中所支援的DB、DW、DD、DQ等四條彙編指令(在Borland的Delphi6官方語言手冊裡只說支援DB、DW、DD)插入相關指令的數值表示也能靈活的實作。
如:
DW $A20F //CPUID
DW $770F //EMMS
DB $0F, $6F, $C1 //MOVQ MM0, MM1
了解指令只是基礎,在圍繞FPU,MMX,SSE設計完演算法後,想更深一步的進行最佳化,還必須了解一些CPU本身的技術特性。
先看看下面兩段程式碼:
asm
ADD [a], ECX
ADD [b], EDX
end
asm
MOV EAX, [a]
MOV EBX, [b]
ADD EAX, ECX
ADD EBX, EDX
MOV [a], EAX
MOV [b], EBX
end
第二個效率高?錯了,如上面說的,指令少不意味著執行效率高,查查相關資料可知,第一段程式碼的兩條指令執行的時脈週期為3(每條指令都需要完成讀、改、寫三步),第二段程式碼中的6條指令執行的時脈週期都為1。那麼說兩段程式碼效率一樣?又錯了,實際上第二段程式碼執行效率比第一段程式碼高!為什麼?因為奔騰級以後的CPU都有兩條管線來執行指令,所以當相鄰的兩條指令能夠完成配對,那麼它們就能夠同時執行!具體到上面的兩段程式碼來說具體原因又是什麼呢?
第一段程式碼中的兩個指令雖然可以完成配對,但需要的總執行時鐘週期為5而不是3,而第二段程式碼的六條指令可以兩兩之間並行執行,所以也就導致了這個結果。
說到這裡,都是些很淺顯的例子,本身給不了大家太多的幫助。如果真的想優化特定程序,還是找些FPU,MMX優化的專題文章看看,或者找來技術手冊好好專研專研「亂序執行」和「分枝預測」等技術。只希望各位在上大學的朋友們不要只專注於那些「能賺錢」的開發工具和時髦的新技術,能把更多的時間花在打基礎上,有了紮實的基礎才能快速掌握新知識、才能用更快的時間掌握新的開發工具、才能...(省略一千字)。
不過話又說回來,知識還是要用來解決實際問題的,如果每天就只在技術細節上做文章,也許能成為一個出色的黑客,但絕對開發不出一流的軟體。所以還是要以創造價值為根本目的。所以...不說了,再說下去就真不像技術文章了。 ^_^
附:程式最佳化除了考慮執行效率以外,當然也要考慮體積的問題(體積小才能更快的載入內存,更快的完成指令譯碼等工作),例如清空EAX暫存器都是用SUB EAX, EAX或XOR EAX, EAX而不會用MOV EAX, $0,雖然它們的執行時鐘週期都是1,但前者的指令長度(2位元組)明顯比後者(5位元組)短。但因為上面說的都是些細節,所以沒提到體積的問題。更多的縮小體積的問題還是交給編譯器去解決吧,在編寫嵌入ASM程式碼的同時稍微注意一下就可以了。