2010-06-29 40 views
4

我使用的是帶有Unicode字符串的Delphi 2009。編碼超大文件時如何解決此EOutOfMemory異常?

我試圖編碼非常大的文件,將其轉換爲Unicode:

var 
    Buffer: TBytes; 
    Value: string; 

Value := Encoding.GetString(Buffer); 

這工作得很好,可推動在規模擴大了一倍40 MB緩衝區,並返回值作爲80 MB Unicode字符串。

當我嘗試300 MB緩衝區時,它給我一個EOutOfMemory異常。

那麼,這並非完全意外。但我決定無論如何追蹤它。

它進入系統單元的DynArraySetLength過程。在那個過程中,它進入堆並調用ReallocMem。令我驚訝的是,它成功分配了665,124,864字節!

但隨後朝着DynArraySetLength年底,它調用FillChar:

// Set the new memory to all zero bits 
    FillChar((PAnsiChar(p) + elSize * oldLength)^, elSize * (newLength - oldLength), 0); 

您可以通過什麼是應該做的評論看。這個例程沒有太多,但這是導致EOutOfMemory異常的例程。這裏是從系統單位FillChar:

procedure _FillChar(var Dest; count: Integer; Value: Char); 
{$IFDEF PUREPASCAL} 
var 
    I: Integer; 
    P: PAnsiChar; 
begin 
    P := PAnsiChar(@Dest); 
    for I := count-1 downto 0 do 
    P[I] := Value; 
end; 
{$ELSE} 
asm         // Size = 153 Bytes 
     CMP EDX, 32 
     MOV CH, CL     // Copy Value into both Bytes of CX 
     JL @@Small 
     MOV [EAX ], CX   // Fill First 8 Bytes 
     MOV [EAX+2], CX 
     MOV [EAX+4], CX 
     MOV [EAX+6], CX 
     SUB EDX, 16 
     FLD QWORD PTR [EAX] 
     FST QWORD PTR [EAX+EDX] // Fill Last 16 Bytes 
     FST QWORD PTR [EAX+EDX+8] 
     MOV ECX, EAX 
     AND ECX, 7     // 8-Byte Align Writes 
     SUB ECX, 8 
     SUB EAX, ECX 
     ADD EDX, ECX 
     ADD EAX, EDX 
     NEG EDX 
@@Loop: 
     FST QWORD PTR [EAX+EDX] // Fill 16 Bytes per Loop 
     FST QWORD PTR [EAX+EDX+8] 
     ADD EDX, 16 
     JL @@Loop 
     FFREE ST(0) 
     FINCSTP 
     RET 
     NOP 
     NOP 
     NOP 
@@Small: 
     TEST EDX, EDX 
     JLE @@Done 
     MOV [EAX+EDX-1], CL  // Fill Last Byte 
     AND EDX, -2    // No. of Words to Fill 
     NEG EDX 
     LEA EDX, [@@SmallFill + 60 + EDX * 2] 
     JMP EDX 
     NOP       // Align Jump Destinations 
     NOP 
@@SmallFill: 
     MOV [EAX+28], CX 
     MOV [EAX+26], CX 
     MOV [EAX+24], CX 
     MOV [EAX+22], CX 
     MOV [EAX+20], CX 
     MOV [EAX+18], CX 
     MOV [EAX+16], CX 
     MOV [EAX+14], CX 
     MOV [EAX+12], CX 
     MOV [EAX+10], CX 
     MOV [EAX+ 8], CX 
     MOV [EAX+ 6], CX 
     MOV [EAX+ 4], CX 
     MOV [EAX+ 2], CX 
     MOV [EAX ], CX 
     RET       // DO NOT REMOVE - This is for Alignment 
@@Done: 
end; 
{$ENDIF} 

所以我的記憶被分配,但它墜毀試圖用零填充它。這對我沒有意義。就我而言,內存甚至不需要用零來填充 - 無論如何這可能是浪費時間的 - 因爲無論如何Encoding語句都將填充它。

我可以以某種方式防止德爾福做記憶填充嗎?

或者有沒有其他方法可以讓Delphi爲我成功分配這個內存?

我的真正目標是爲我的非常大的文件做這個Encoding語句,所以任何解決方案,這將是非常讚賞。


結論:請參閱我對答案的評論。

這是在調試彙編代碼時要小心的警告。確保你在所有的「RET」行中都被打破,因爲我錯過了FillChar例程中的一個,並錯誤地推斷FillChar導致了這個問題。感謝梅森,指出這一點。

我將不得不將輸入分解爲塊以處理非常大的文件。

+0

很高興我能幫上忙。 :) – 2010-06-29 03:13:21

回答

5

從文件中讀取一個塊,編碼並寫入另一個文件,重複。

+0

@Romain:我原本有這樣的代碼。但是在你分解邊界的地方很棘手,因爲你可能會分裂一個多字節的輸入字符。此外,Encoding例程的速度非常快,但不要一次完成所有內容。 – lkessler 2010-06-29 02:32:25

+1

@伊克斯勒 - 有時你必須隨時間或空間的妥協。如果您一次讀入4k或更多,性能不應該那麼差。 – 2010-06-29 02:44:50

+1

...或者甚至是40 MB,因爲您似乎能夠處理該問題。 – 2010-06-29 03:06:04

6

FillChar沒有分配任何內存,所以這不是你的問題。嘗試追蹤它並在RET語句中放置斷點,您將看到FillChar完成。無論問題是什麼,它可能在後面的一步。

+0

謝謝你。是的,你是對的。 FillChar程序中間的RET語句就是它離開的地方,所以我在程序結束時的斷點並沒有捕捉到它。它然後到達MemoryManager.GetMem併發出OutOfMemory錯誤信號。我必須將編碼分成像@Romain所說的塊。你幫了我,但羅曼回答我的問題,所以我必須給他接受的答案。 – lkessler 2010-06-29 03:00:04

+2

+1幫助他出局 – 2010-06-29 03:10:40

1

瘋狂的猜測:問題是內存是否被過度使用,當FillChar實際訪問內存時,它找不到實際提供的頁面?我不知道Windows是否會過度使用內存,但我知道有些操作系統會這樣做 - 直到您真的嘗試使用內存時纔會發現它。

如果是這種情況,可能會導致FillChar爆炸。

+0

感謝您的迴應,但FillChar終究不是問題,因爲@梅森是正確的指出。 – lkessler 2010-06-29 03:03:33

1

程序在循環中很棒。他們孜孜不倦地循環而不抱怨。

分配大量內存需要時間。會有很多電話給堆管理員。您的操作系統甚至不會知道您是否有提前需要的連續內存量。你的操作系統說,是的,我有1 GB免費。但是一旦你使用它,你的操作系統就會說,等一等,你想把它全部放在一塊?讓我確保我在一個地方有足夠的所有。如果它沒有,你會得到錯誤。

如果確實有內存,那麼堆管理器在準備內存並將其標記爲已使用時仍有許多工作要做。

因此,顯然,分配較少的內存並簡單地循環它是有道理的。這樣可以避免計算機執行大量工作,只有在計算完成後才能進行撤消。爲什麼不做一點點的工作來擱置你的記憶,然後繼續使用它呢?

堆內存分配速度比堆內存快得多。如果你保持你的內存使用率很小(默認情況下低於1MB),編譯器可能會在堆內存中使用堆棧內存,這將使你的循環更快。另外,寄存器中分配的局部變量非常快。

存在硬盤驅動器集羣和高速緩存大小,CPU高速緩存大小以及事物等因素,它們提供了有關最佳塊大小的提示。關鍵是要找到一個好的數字。我喜歡使用64 KB的塊。

+0

這是一個很好的評論。我會嘗試使用40 MB和1 MB作爲阻塞大小,並測試更多堆棧分配是否比堆分配更少。 – lkessler 2010-06-29 05:28:21

+0

這個想法是在你使用它時分配內存,但是在堆棧上分配。如果你重複地調用一個函數,在棧上分配內存然後釋放它,你仍然在做額外的工作。在函數內循環使用for或while循環以重用內存。 – 2010-06-29 14:19:47