Delphi 程式碼最佳化——字串篇 (轉)

worldblog發表於2007-12-03
Delphi 程式碼最佳化——字串篇 (轉)[@more@]

關鍵詞:, AnisString, Pchar
freewizard

Delphi有三種字串型別:短字串(String[n],n=1..255)區為靜態分配,大小在編譯時確定,這是繼承於BP for Dos的型別;字元陣列(PChar)主要是為了相容各類,在BP7中已經出現,如今在Delphi中更加應用廣泛,其儲存區可以用字元陣列靜態分配,也可用GetMem手動分配;而長字串(AnsiString)是Delphi獨有的,其儲存區在執行時動態分配,最靈活也最易被濫用。

不重複初始化
Delphi預設字串型別AnsiString會自動初始化為空。如下程式碼:

var s:string;
begin
s:=';
……
end;
s:=';就屬多此一舉。但是值得注意的是這對返回值Result無效。而一般說來,用var實參傳遞比返回字串值要更快一些。

使用SetLength預分配長字串(AnsiString)
動態分配是AnsiString的一大長項,但容易弄巧成拙,一個典型的例子如下:
s2:=;
for i:=2 to length(s1) do s2:=s2+s1[i];
且不說可用Delete取代之,主要問題在於上例的迴圈中s2的記憶體區域被不停地重複分配,相當費時。一個簡單有效的辦法如下:
SetLength(s2,length(s1)-1);
for i:=2 to length(s1) do s2[i-1]:=s1[i];
這樣s2記憶體只會重新分配一次。

字串與動態陣列的執行緒(Thread Safety)
在Delphi 5以前動態陣列與長字串的操作這些非執行緒安全是由引用計數來處理其臨界問題的,而自Delphi5起就改為直接在一些臨界指令前加lock指令字首來避免這個問題。不幸的是這一修改的代價相當昂貴,因為在PentiumⅡ中lock指令相當費時,大概要耗費額外的28個指令週期來完成這一操作,因而整體至少下降一半。
解決這個問題的辦法只有一個,那就是修改Delphi RTL核心程式碼。在原後,將rtlsyssystem.pas中所有的lock替換為{lock},當然必須是整字替換。
如此還未完全,下一步是將Delphi4執行庫中也有的xchg指令去掉,因為該指令有隱含的lock字首,所以必須將system.pas內_LstrAsg和_StrLAsg兩個過程中的 XCHG EDX,[EAX] 替換為如下程式碼:
mov ecx,[eax]
mov [eax],edx
mov edx,ecx
OK大功告成,編譯一下,覆蓋system.dcu即可。如此其效率將比Delphi5提高6倍,比Delphi4提高2倍。

避免使用短字串
由於很多字串操作會先把短字串轉換為長字串,從而減慢了執行速度,因此還是少使用短字串為妙。

避免使用Copy函式
這也和濫用記憶體管理有關。一個典型的情形如下:
if Copy(s1,23,64)=Copy(s2,15,64) then ……
這樣導致分配了兩塊臨時記憶體,因而降低了效率。應當替換為如下程式碼:
i:=0;
f:=false;
repeat
 f:=s1[i+23]<>s2[i+15];
 inc(i);
until f or (I>63);
if not f then ……
同樣的,如下語句就顯得相當低效:
s:=Copy(s,1,length(s)-10);
應改為
Delete(s,length(s)-10,10);
順便提一句,在連線字串時,s:=s1+s2;簡單而有效;但在Delphi2下則s:=Format([%s%s],s1,s2);可能稍快些。

總是使用長字串,必要時轉換為Pchar
先看看AnsiString的定義:
type
  Astring = packed record
  allocSiz: Longint;  //動態分配大小
  refCnt: Longint;  //引用計數
  length: Longint;  //實際長度
  ChrArr:array[1..allocsiz-6]of char;  //位元組序列
  end;
其中Astring[1]將返回Astring.ChrArr[1]的內容。
很多人認為AnsiString是天生低效的。其實這在很大程度上是由程式碼編寫不良、記憶體管理亂用和缺乏支援的函式所致。如上所述,一旦被動態分配了一塊記憶體,長字串就成了一個線性的位元組序列,並無所謂的效率問題。當然,若有更多有效的函式支援那就更好了。
說到AnsiString到PChar的轉換,本質上有三個辦法:
(1) P:=@s[1];這會引發UniqueString呼叫。
(2) P:=PChar (s);這會先檢查s是否為空,若是,則返回nil,否則即返回s[1]的地址。
(3) P:=Pointer(s);這不會引發任何隱含呼叫,因而是在確定s非空情況下的最佳選擇。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-987732/,如需轉載,請註明出處,否則將追究法律責任。

相關文章