Delphi 代碼優化

Delphi強力優化

    nightmare(qingrui li)

**關於記錄類型的返回值
C++程序員不會這樣做,因爲返回值會被壓入堆棧,導致時間效率和空間效率都降低。但Delphi無此問題。一般情況下,記錄類型返回值會像Out參數一樣傳遞引用(在EAX寄存器中)。

**公共表達式
Delphi只在一個語句行內提取公共表達式。如果公共表達式包含函數調用,則不會被提取,因爲可能改變語義。如果確保函數返回值相同,應預先計算。

**類屬性
每次引用屬性時都會使屬性重新計算,即使屬性是直接映射到域。頻繁調用屬性時應預先計算。

**循環內的重複計算表達式外提
C++程序員可能會讓編譯器做此優化,但Delphi一般不會這麼做。最安全且最有效的優化應由程序員來完成。

**使用const參數
當函數參量不被改變時,如果參量長度超過4字節或是接口、字符串、動態數組類型,使用const參數,可以使編譯器儘可能的以地址方式用寄存器傳遞。

**長度爲4字節的記錄或數祖賦值時,編譯器會自動按DWORD用32位寄存器處理

**記錄數組的元素長度是偶數的話,可以生成更快的數組訪問代碼

**整除2或4會優化成右移位,乘2或4會優化成左移位,所以不需特意用移位運算,可以寫出更可讀的代碼

**如果你在寫大量浮點運算的程序,記住,Delphi不對浮點計算做優化。手工優化,必要時用匯編,或者考慮用C(不是C++)。

**with語句
with語句不止是源代碼上的簡潔,而且生成更快更短的代碼。

**Delphi 6 的Alignment
Delphi 6 缺省按8字節對齊變量邊界,在工程選項裏將其改成4字節可以使生成的應用程序更短小。

**不要爲Delphi裏沒有宏而抱怨
參數較少的函數,調用時只耗1到2個時鐘週期(register調用協議的優勢),因爲沒有參數壓棧的開銷。

 

 

Delphi代碼優化(二) 整數篇

儘量使用32位變量

在32位代碼中,32位變量是默認處理格式;16位變量(word,shortint,widechar)的運算會令處理器臨時切換爲16位處理模式,因而需要雙倍的處理時間;相較之下,8位變量(byte,char)只要不與其它混用,卻不會太慢。如果實在需要多次使用一個8或16位變量,可以考慮把它臨時轉換成32位變量,這只需要一步賦值:ADWord:=Aword;

避免使用子域類型
Pascal語言的一大優勢便是其豐富的數據類型,Delphi之Object Pascal繼承了這一傳統,枚舉和子域類型即屬此類。但不幸的是,他們會爲優化帶來麻煩,因爲它們的佔用的字節數取決於其子域的大小。比如一個元素數不超過256個的枚舉類型會佔用1個字節,而例如MyYear=1900..2000則會佔用兩個字節,而如前文所述,16位變量是很慢的。

簡化表達式
過於複雜的表達式會妨礙編譯器的自動優化,這時可以考慮引入臨時變量來化簡表達式,這樣可能(!)可以得以優化,更重要的是提高了代碼的可讀性。

不再畏懼乘法
PII出現以前,乘法運算是相當費時的,以至於當時的經典優化方法便是把一類特殊的乘法轉變爲移位運算和加法。而今,在作爲標準配置的PII上,乘法和多數其它運算一樣,只需要一個指令週期即可完成。當然Delphi編譯器仍然會把諸如*2之類的運算優化爲shl 1,這也不壞,不是嗎?

臨時子域類型
才揭過子域類型的短,又來說它的妙用:-p 但這也不是真正的子域類型,不過是形式上相似罷了。像以下的語句:
if ((x>=0) and (x=<10)) or ((x>=20) and (x<=30)) then …
可以改寫爲:
if x in [0..10,20..30] then …
子域數越多,優化效果越明顯。不過除了在NOI題目裏以外,天下可沒有免費餡餅,這回的代價是佔用一個臨時寄存器。

movzx 與 xor/mov
這是讀入小於32位數據的兩種不同方法,後者在PII以前更具優勢,而前者在PII上因其亂序執行的特性而顯得更有效率。編譯器對此的取捨規則似乎很複雜,必要時還是自己用嵌入彙編好了。

大整數運算
對付大整數(超過32位的),你有四種武器(爲什麼不是七種?問Borland,別來問我)——int64、comp、double和extended。其中除了64位整數類型int64外,其餘都是浮點數,其運算都是由FPU指令實現的。這其中的comp類型,存儲結構同int64一模一樣,按照 Borland的官方說法,comp類型已經過時,應當被int64所取代,理由很簡單——整數運算總比浮點快吧。然而根據一項在PII上進行的測試, int64除了在加減運算中具有無可比擬的優勢外,在乘除方面,竟比浮點數還慢!

好在還有老當益壯的comp,只是稍有些繁瑣。
首先將變量聲明爲int64,並聲明兩個輔助元:
var a,b,c,d,e: int64;
 ca:comp absolute a;
 cc:comp absolute c;
加減法不用變,除法就如下處理:
c:=trunc(ca/b); //is faster than c:= a div b
乘法這麼來:
e:=round(ca*b+cc*d); //is faster than e:=a*b+c*d;

Delphi代碼優化浮點篇
忘掉extended
  extended很大(10字節,如果代碼對齊就有12字節),讀寫運算都很慢,是優化的大敵。且Delphi2-4對extended的代碼對齊有bug。因此,若非必要,不要用extended。
  同時,在混合浮點類型的運算中,編譯器爲了不丟失精度,臨時變量以extended類型存儲,所以要避免混合浮點運算。
  還有,用const定義的常量,如不加指明,則也默認爲extended類型。解決辦法是,配合$J指示字,定義指明類型常量(typed constand)。

變FPU控制字
  默認的FPU控制字令除法運算和PII/PIII上的平方根運算慢而精確,當無須得到這樣的結果時,可用Set8087CW讓FPU“偷懶”。

對於Single類型:Set8087CW(Default8087CW and $FCFF)
對於Double類型:Set8087CW((Default8087CW and $FCFF) or $0200)
對於extended類型:Set8087CW(Default8087CW or $0300)

多用Round
  Trunc會讀寫FPU指令字,而Round不會,所以可以的話,儘量用Round。

傳送實參
  對於返回浮點值的函數,入口和出口處會有附加的壓棧退棧,對形如:
function func(x : SomeType): SomeFloat;
  不妨改寫爲:
procedure func(x : SomeType; var fp : SomeFloat);
  對於在過程中未修改的浮點形參,沒必要用const修飾,因爲那除了增加一個編譯期檢查外,別無用處。相應的對策是用var修飾爲實參,強制傳址。

自己動手,豐衣足食
  Delphi本身不對浮點運算作任何優化,因此很多時候,還得自己用匯編來解決。
  值得注意的是,Delphi中浮點異常的觸發,不是在出錯之後,而是在下一條浮點指令之前。因此,通常的作法是,在一次浮點操作完畢後,加一條FWAIT指令。

減少除法
  除法,即多次的減法,其代價是相當昂貴的,因而有必要減少除法的次數。
  另外,對於簡單除法(如:a/5),編譯器不一定(?!)會將其變爲乘法(a*0.2),比如:
  fp:=fp*3*4/5+3*4/2;
  在Delphi 4中,會被編譯爲:
  fp:=fp*3*4/5+6;
  而只有:
  fp:=3*4/5*fp+3*4/2;
  纔會被編譯爲:
  fp:=2.4*fp+6;
  鑑於編譯器的繁複規則,建議這一步優化自己完成。

浮點零的檢查

  檢查一個浮點數是否爲零,如果簡單的“Afloat=0”,會把0轉換爲浮點零。而更好的辦法是這樣:
  對於Single類型:(Dword(pointer(Asingle))shl 1) =0
  對於Double類型:

type
  DoubleData=record lo,hi:Dword end;
Var
  ADouble:Double;
  Dd:DoubleData absolute Adouble;
begin
  …
  if ((dd.hi shl 1)+dd.lo)=0 then …
end;
  此法在PII上有30%-40%的效率提升。

發佈了7 篇原創文章 · 獲贊 2 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章