我正在使用带有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导致了这个问题。谢谢梅森,指出这一点。
我必须将输入分解为块以处理非常大的文件。
答案 0 :(得分:6)
FillChar没有分配任何内存,所以这不是你的问题。尝试跟踪它并在RET语句中放置断点,你会看到FillChar完成。无论问题是什么,都可能在后面的步骤中进行。
答案 1 :(得分:5)
从文件中读取一个块,编码并写入另一个文件,重复。
答案 2 :(得分:1)
一个疯狂的猜测:问题可能是内存被过度使用,当FillChar实际访问内存时,它无法找到实际给你的页面吗?我不知道Windows是否会过度使用内存,我知道有些操作系统会这样做 - 在你真正尝试使用内存之前,你还没有发现它。
如果是这种情况,可能会导致FillChar爆炸。
答案 3 :(得分:1)
程序非常适合循环。他们不假思索地不停地抱怨。
分配大量内存需要时间。会有很多对堆管理器的调用。您的操作系统甚至不知道它是否具有您需要的连续内存量。你的操作系统说,是的,我有1 GB免费。但是一旦你去使用它,你的操作系统会说,等等,你想把它全部放在一个块中吗?让我确保我在一个地方有足够的东西。如果没有,则会收到错误。
如果确实有内存,那么堆管理器在准备内存并将其标记为已使用时仍有很多工作。
所以,显然,分配更少的内存并简单地循环它是有道理的。这样可以避免计算机执行大量工作,只需完成即可撤消。为什么不放弃你的记忆只做一点工作,然后继续重复使用呢?
堆栈内存的分配比堆内存快得多。如果你的内存使用量很小(默认情况下低于1 MB),编译器可能只使用堆内存超过堆内存,这将使你的循环更快。此外,在寄存器中分配的局部变量非常快。
有一些因素,例如硬盘驱动器群集和缓存大小,CPU缓存大小以及提供最佳块大小提示的内容。关键是要找到一个好的数字。我喜欢使用64 KB的块。