2010-03-13 67 views
4

我的應用程序根據文件名(包括其他基於字符串的信息)在內存中構建了很多對象。我希望通過分別存儲路徑和文件名來優化內存使用,然後在同一路徑中的對象之間共享路徑。我沒有試圖看看使用字符串池或任何東西,基本上我的對象被排序,所以如果我有10個對象具有相同的路徑我希望對象2-10的路徑「指向」對象1的路徑(例如對象[2]。路徑=對象[1]。路徑);我認爲我告訴他們(通過對象[2] .Path = object [1]),我不相信我的對象事實上共享對同一個字符串的引用。 .Path分配)。Delphi中對象的字符串共享/引用問題

當我用字符串列表進行實驗並將所有值設置爲列表中的第一個值時,我可以看到「內存保護」的作用,但是當我使用對象時,我完全沒有看到任何變化,我承認我只使用任務管理器(私人工作集)來觀察內存使用的變化。

這是一個人爲的例子,我希望這是有道理的。

我有一個對象:

TfileObject=class(Tobject) 
    FpathPart: string; 
    FfilePart: string; 
end; 

現在我創建對象100萬分的情況下,使用每一個新的字符串:

var x: integer; 
MyFilePath: string; 
fo: TfileObject; 
begin 
    for x := 1 to 1000000 do 
    begin 
    // create a new string for every iteration of the loop 
    MyFilePath:=ExtractFilePath(Application.ExeName); 
    fo:=TfileObject.Create; 
    fo.FpathPart:=MyFilePath; 
    FobjectList.Add(fo); 
    end; 
end; 

運行這件事和任務經理說我使用68MB的內存什麼的。 (請注意,如果我在循環之外分配了MyFilePath,那麼由於字符串的一個實例,我確實節省了內存,但這是一個人爲的例子,實際上並不是在應用程序中發生的情況)。

現在我想通過使所有對象共享的路徑字符串的同一實例「優化」我的內存使用情況,因爲它是相同的值:

變種X:整數;對於x:= 1到FobjectList.Count-1做 開始 做 開始 TfileObject(FobjectList [x])。FpathPart:= TfileObject(FobjectList [0])。FpathPart; 結束; 結束;

任務管理器顯示沒有變化。

但是,如果我做一個的TStringList類似的事情:

var x: integer; 
begin 
    for x := 1 to 1000000 do 
    begin 
    FstringList.Add(ExtractFilePath(Application.ExeName)); 
    end; 
end; 

任務管理器說60MB的內存使用。

現在與優化:

var x: integer; 
begin 
    for x := 1 to FstringList.Count - 1 do 
    FstringList[x]:=FstringList[0]; 
end; 

任務管理器顯示內存使用量的下降,我所期望的,現在10MB。

所以我似乎能夠在字符串列表中共享字符串,但不能在對象中共享字符串。我明顯錯過了一些概念上的代碼或兩者!

我希望這是有道理的,我真的可以看到使用這種技術來節省內存的能力,因爲我有很多對象都有大量的字符串信息,數據以許多不同的方式排序,我希望成爲能夠在將這些數據加載到內存中後迭代這些數據,並通過以這種方式共享字符串來再次釋放部分內存。

在此先感謝您提供的任何幫助。

PS:我用Delphi 2007,但我剛剛測試了德爾福2010年,其結果都是一樣的,不同的是德爾福2010使用兩倍的內存,由於unicode字符串...

回答

4

當你的Delphi程序分配和釋放內存時,它不是直接使用Windows API函數,而是通過內存管理器。你在這裏觀察到的事實是內存管理器在程序中不再需要時將所有分配的內存釋放回操作系統。它將保留部分或全部以備後用,以加快應用程序中稍後的內存請求。因此,如果使用系統工具,內存將按程序分配列出,但它並未處於活動狀態,它在內部標記爲可用,並存儲在MM將用於任何其他內存的可用內存塊列表中在程序進入操作系統並請求更多內存之前,在您的程序中進行分配。

如果要真正檢查程序的任何更改如何影響內存消耗,則不應依賴外部工具,而應使用內存管理器提供的診斷程序。下載完整的FastMM4版本並將其作爲DPR文件中的第一個單元用於您的程序中。您可以使用GetMemoryManagerState()函數獲取詳細信息,該函數將告訴您使用了多少個小型,中型和大型內存塊以及爲每個塊大小分配了多少內存。然而,對於快速檢查(這裏將完全充分),您可以簡單地調用GetMemoryManagerUsageSummary()函數。它會告訴你分配的內存總量,如果你打電話給你,你會看到你的重新分配FPathPart確實釋放了幾MB的內存。

當使用TStringList時,您會觀察到不同的行爲,並且所有字符串都按順序添加。這些字符串的內存將從較大的塊中分配,而這些塊將不包含其他內容,因此可以在釋放字符串列表元素時再次釋放它們。如果OTOH創建了對象,那麼這些字符串將與其他數據元素交替分配,因此釋放它們將在較大的塊中創建空的內存區域,但這些塊不會被釋放,因爲它們還包含其他內容的有效內存。你基本上增加了內存碎片,這本身可能是一個問題。

+0

感謝mghie,這解釋了由於TstringList和對象之間的不同行爲而導致的答案。 – Jenakai 2010-03-14 00:18:30

0

因爲任務管理器不告訴你全部的事實。與此代碼比較:

var 
    x: integer; 
    MyFilePath: string; 
    fo: TfileObject; 
begin 
    MyFilePath:=ExtractFilePath(Application.ExeName); 
    for x := 1 to 1000000 do 
    begin 
    fo:=TfileObject.Create; 
    fo.FpathPart:=MyFilePath; 
    FobjectList.Add(fo); 
    end; 
end; 
+0

Pham,謝謝,我知道這個。這絕對有效,每個對象只存儲一個「MyFilePath」實例和大量減少的內存使用量。我正在看的是在第一次分配之後設置路徑。其中一個主要原因是我有很多從流中加載的對象,並且當時每個對象都有自己的版本。我只是希望迭代列表並將它們設置爲字符串的「共享」版本。任務管理器可以識別Tstringlist發生這種情況的時間,而不是對象。 – Jenakai 2010-03-13 22:57:43

1

正如另一個答案所指出的那樣,未被使用的內存並不總是通過Delphi內存管理器立即釋放到系統中。

您的代碼通過動態增長對象列表來保證大量此類內存。

TObjectList(共同與的TList的TStringList)使用增量內存分配器。其中一個容器的新實例以分配給4個項目的內存(容量)開始。當添加的項目數量超過容量分配額外的內存時,最初將容量加倍,然後一旦達到一定數量的項目,則將容量增加25%。

每次計數超過容量,附加的存儲器分配,當前存儲器複製到新的存儲和釋放的先前使用的存儲器(它是這個存儲器,它不立即返回到系統)。

當你知道有多少項目將被加載到這些類型的列表,你可以避開這個內存重新分配行爲(和實現顯著的性能提升),通過相應預分配列表容量之一。

您不一定必須設置所需的精確容量 - 最佳猜測(更可能接近或高於實際需要的數字仍然會優於初始缺省容量4如果項目的數量顯着> 64)

+0

+1這個不錯的提示! – jpfollenius 2010-03-15 09:21:04

0

要共享引用,需要直接指定字符串並且類型相同(很明顯,您不能在UnicodeString和AnsiString之間共享引用)。

我能想到的達到你想要什麼,最好的方法是如下:

var StrReference : TStringlist; //Sorted 

function GetStrReference(const S : string) : string; 
var idx : Integer; 
begin 
    if not StrReference.Find(S,idx) then 
    idx := StrReference.Add(S); 
    Result := StrReference[idx]; 
end; 

procedure YourProc; 
var x: integer; 
MyFilePath: string; 
fo: TfileObject; 
begin 
    for x := 1 to 1000000 do 
    begin 
    // create a new string for every iteration of the loop 
    MyFilePath := GetStrReference(ExtractFilePath(Application.ExeName)); 
    fo   := TfileObject.Create; 
    fo.FpathPart := MyFilePath; 
    FobjectList.Add(fo); 
    end; 
end; 

要確保它工作正常,你可以調用StringRefCount(單位系統)功能。我不知道引入了哪個版本的delphi,所以這是當前的實現。

function StringRefCount(const S: UnicodeString): Longint; 
begin 
    Result := Longint(S); 
    if Result <> 0 then 
    Result := PLongint(Result - 8)^; 
end; 

讓我知道它是否按照你想要的那樣工作。

編輯:如果你怕的StringList增長過大的,你可以放心地定期掃描,並從列表中的任何字符串的1

列表中刪除StringRefCount可以擦拭乾淨太...但是,這將使該函數保留傳遞給該函數的任何新字符串的新副本。