编码非常大的文件时如何解决此EOutOfMemory异常?

时间:2010-06-29 02:14:37

标签: delphi unicode encoding large-files

我正在使用带有Unicode字符串的Delphi 2009。

我正在尝试编码一个非常大的文件,将其转换为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}

所以我的记忆被分配了,但它崩溃了,试图用零填充它。这对我来说没有意义。就我而言,内存甚至不需要用零填充 - 无论如何这可能是浪费时间 - 因为编码语句无论如何都要填充它。

我可以以某种方式阻止Delphi进行内存填充吗?

或者还有其他方法可以让Delphi成功为我分配这个内存吗?

我的真正目标是为我的大文件执行Encoding语句,因此任何允许这样做的解决方案都会非常感激。


结论:请参阅我对答案的评论。

这是在调试汇编程序代码时要小心的警告。确保你打破了所有的“RET”线,因为我错过了FillChar例程中间的那个,并错误地断定FillChar导致了这个问题。谢谢梅森,指出这一点。

我必须将输入分解为块以处理非常大的文件。

4 个答案:

答案 0 :(得分:6)

FillChar没有分配任何内存,所以这不是你的问题。尝试跟踪它并在RET语句中放置断点,你会看到FillChar完成。无论问题是什么,都可能在后面的步骤中进行。

答案 1 :(得分:5)

从文件中读取一个块,编码并写入另一个文件,重复。

答案 2 :(得分:1)

一个疯狂的猜测:问题可能是内存被过度使用,当FillChar实际访问内存时,它无法找到实际给你的页面吗?我不知道Windows是否会过度使用内存,我知道有些操作系统会这样做 - 在你真正尝试使用内存之前,你还没有发现它。

如果是这种情况,可能会导致FillChar爆炸。

答案 3 :(得分:1)

程序非常适合循环。他们不假思索地不停地抱怨。

分配大量内存需要时间。会有很多对堆管理器的调用。您的操作系统甚至不知道它是否具有您需要的连续内存量。你的操作系统说,是的,我有1 GB免费。但是一旦你去使用它,你的操作系统会说,等等,你想把它全部放在一个块中吗?让我确保我在一个地方有足够的东西。如果没有,则会收到错误。

如果确实有内存,那么堆管理器在准备内存并将其标记为已使用时仍有很多工作。

所以,显然,分配更少的内存并简单地循环它是有道理的。这样可以避免计算机执行大量工作,只需完成即可撤消。为什么不放弃你的记忆只做一点工作,然后继续重复使用呢?

堆栈内存的分配比堆内存快得多。如果你的内存使用量很小(默认情况下低于1 MB),编译器可能只使用堆内存超过堆内存,这将使你的循环更快。此外,在寄存器中分配的局部变量非常快。

有一些因素,例如硬盘驱动器群集和缓存大小,CPU缓存大小以及提供最佳块大小提示的内容。关键是要找到一个好的数字。我喜欢使用64 KB的块。