Delphi 在编码一个非常大的文件时,如何避免这种内存异常?

Delphi 在编码一个非常大的文件时,如何避免这种内存异常?,delphi,unicode,encoding,large-files,Delphi,Unicode,Encoding,Large Files,我正在使用Delphi2009和Unicode字符串 我正在尝试对一个非常大的文件进行编码,以将其转换为Unicode: var Buffer: TBytes; Value: string; Value := Encoding.GetString(Buffer); 这对于40MB的缓冲区很好,该缓冲区的大小增加了一倍,并以80MB Unicode字符串的形式返回值 当我使用300 MB的缓冲区尝试此操作时,它会给我一个内存异常 嗯,这并不完全出乎意料。但我还是决定追查到底 它进入系统

我正在使用Delphi2009和Unicode字符串

我正在尝试对一个非常大的文件进行编码,以将其转换为Unicode:

var
  Buffer: TBytes;
  Value: string;

Value := Encoding.GetString(Buffer);
这对于40MB的缓冲区很好,该缓冲区的大小增加了一倍,并以80MB Unicode字符串的形式返回值

当我使用300 MB的缓冲区尝试此操作时,它会给我一个内存异常

嗯,这并不完全出乎意料。但我还是决定追查到底

它进入系统单元中的DynArraySetLength过程。在该过程中,它进入堆并调用ReallocMem。令我惊讶的是,它成功地分配了665124864字节

但在DynArraySetLength的末尾,它调用FillChar:

  // Set the new memory to all zero bits
  FillChar((PAnsiChar(p) + elSize * oldLength)^, elSize * (newLength - oldLength), 0);
你可以从评论中看到这应该做什么。该例程没有太多内容,但正是该例程导致了内存异常。以下是来自系统单元的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为我成功分配内存

我真正的目标是为我的非常大的文件做那个编码语句,所以任何允许这样做的解决方案都将不胜感激


结论:见我对答案的评论

这是在调试汇编代码时要小心的警告。确保你打破了所有的“RET”线,因为我错过了一个在FILCHAR例程中间,错误地得出结论,菲尔查尔造成的问题。谢谢梅森指出这一点


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

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

FillChar没有分配任何内存,因此这不是您的问题。尝试跟踪它并在RET语句处放置断点,您将看到FillChar完成。不管问题是什么,都可能是在以后的步骤中。

一个粗略的猜测:问题可能是内存过度使用,而当FillChar实际访问内存时,它找不到实际提供给您的页面?我不知道Windows是否会过度使用内存,我知道有些操作系统会这样做——直到你真正尝试使用内存,你才会发现


如果是这种情况,它可能会导致FillChar中的放大。

程序擅长循环。他们没完没了地兜圈子,毫无怨言

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

如果它确实有内存,那么堆管理器在准备内存并将其标记为已使用时仍有大量工作要做

因此,显然,分配更少的内存并简单地循环它是有意义的。这使计算机不必做很多只需在完成后撤消的工作。为什么不让它做一点工作,把你的记忆放在一边,然后继续重复使用它呢

堆栈内存的分配速度比堆内存快得多。如果您保持内存使用量较小(默认情况下低于1MB),编译器可能只使用堆栈内存而不是堆内存,这将使循环更快。此外,在寄存器中分配的局部变量非常快


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

@Romain:我最初有代码来做这件事。但在分割边界处很棘手,因为可能会分割多字节输入字符。而且,编码程序速度太快了,不能同时完成这一切真是太遗憾了。@Ikessler-有时候你不得不在时间或空间上做出妥协。如果您一次读取4k或更多,性能应该不会那么差……甚至一次读取40MB,因为您似乎能够处理它。要做的事情是确保它一次可以处理100字节的块,这使得调试变得容易,您可以测试边界条件,然后将其设置为真正大的值(可能是动态的)对于生产代码,我不会读“块”,我会使用流。一个带有读线的快速unicode流应该比300MB的虚拟机快得多。谢谢。是的,你是对的。在FILCHAR程序中间的RET语句是它离开的地方,所以我在程序结束时的休息时间没有赶上它。然后它确实到达MemoryManager.GetMem并发出OutOfMemory错误信号。我将不得不像@Romain所说的那样将编码分割成块。你帮了我的忙,但罗曼回答了我的问题,所以我必须给他一个被接受的答案。谢谢你的回答,但菲尔查尔毕竟不是问题所在,正如@Mason指出的那样。这是一个好的评论。我将尝试使用40 MB和1 MB作为块大小,并测试更多的堆栈分配是否比更少的堆分配快。其想法是在使用内存时保持内存分配,但在堆栈上分配。如果您重复调用一个函数,该函数在堆栈上分配内存,然后释放内存,那么您仍然在做额外的工作。在函数中使用for或while循环来重用内存。