2010-06-29 38 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