在Delphi XE中占用大量内存的对象的TStringList

时间:2011-08-25 16:04:11

标签: delphi memory-management tstringlist

我正在制作模拟程序。

程序所做的第一件事就是在一个巨大的文件中读取(28 mb,大约79'000行),解析每一行(大约150个字段),为对象创建一个类,并将其添加到TStringList中。

它还会读入另一个文件,该文件在运行期间会添加更多对象。最后,它最终成为大约85,000个物体。

我正在使用Delphi 2007,程序使用了大量内存,但运行正常。我升级到Delphi XE,并将程序迁移过来,现在它使用了更多的内存,并且在运行的一半时间内耗尽了内存。

所以在Delphi 2007中,它会在读取初始文件后最终使用1.4演出,这显然是一个巨大的数量,但在XE中,它最终使用了近1.8演出,这真的很大并导致耗尽并得到错误

所以我的问题是

  1. 为什么要使用这么多内存?
  2. 为什么它在XE中使用的内存比2007年多?
  3. 我该怎么办?我不能改变文件的大小,我需要为每一行创建一个对象并将其存储在某个地方
  4. 由于

10 个答案:

答案 0 :(得分:10)

只有一个可以节省记忆的想法。

您可以让数据保留在原始文件上,然后从内存结构中指向它们。

例如,这是我们为browsing big log files almost instantly所做的事情:我们对日志文件内容进行内存映射,然后我们快速解析它以在内存中创建有用信息的索引,然后我们动态地读取内容 / em>的。读取期间不会创建任何字符串。只有指向每一行的指针,动态数组包含所需的索引。调用TStringList.LoadFromFile肯定会慢得多,而且会消耗内存。

代码为here - 请参阅TSynLogFile课程。诀窍是只读取一次文件,并立即生成所有索引。

例如,以下是我们如何从UTF-8文件内容中检索一行文本:

function TMemoryMapText.GetString(aIndex: integer): string;
begin
  if (self=nil) or (cardinal(aIndex)>=cardinal(fCount)) then
    result := '' else
    result := UTF8DecodeToString(fLines[aIndex],GetLineSize(fLines[aIndex],fMapEnd));
end;

我们对parse JSON content使用完全相同的技巧。使用这种混合方法by the fastest XML access libraries

要处理高级数据并快速查询,您可以尝试使用动态记录数组,以及我们优化的TDynArrayTDynArrayHashed包装器(在同一单元中)。记录数组的内存消耗更少,搜索速度更快,因为数据不会被激活(如果使用有序索引或散列,速度会更快),并且您将能够对内容进行高级访问(例如,您可以定义自定义函数以从内存映射文件中检索数据)。动态数组不适合快速删除项目(或者您必须使用查找表) - 但您写道,您没有删除太多数据,因此在您的情况下不会出现问题。

所以你不再有任何重复的结构,只有RAM中的逻辑和内存映射文件中的数据 - 我在这里添加了一个“s”,因为相同的逻辑可以完美地映射到几个源数据文件(你需要一些“合并”和“实时刷新”AFAIK)。

答案 1 :(得分:6)

很难说为什么你的28 MB文件扩展到1.4 GB的对象,当你将它解析为对象时却没有看到代码和类声明。另外,您说您将其存储在TStringList而不是TListTObjecList中。这听起来像你正在使用它作为某种字符串 - >对象键/值映射。如果是这样,您可能需要查看XE中TDictionary单元中的Generics.Collections类。

至于为什么你在XE中使用更多内存,这是因为string类型在Delphi 2009中从ANSI字符串更改为UTF-16字符串。如果你不需要Unicode,你可以使用用于节省空间的TDictionary。

此外,为了节省更多内存,如果你不需要立即拥有所有79,000个对象,还可以使用另一个技巧:延迟加载。这个想法是这样的:

  • 将文件读入TStringList。 (这将使用与文件大小一样多的内存。如果将其转换为Unicode字符串,可能会增加两倍。)不要创建任何数据对象。
  • 当您需要特定数据对象时,请调用检查字符串列表的例程并查找该对象的字符串键。
  • 检查该字符串是否有与之关联的对象。如果没有,请从字符串创建对象并将其与TStringList中的字符串相关联。
  • 返回与字符串关联的对象。

这样可以减少内存使用量和加载时间,但只有在加载后不需要所有(或很大一部分)对象时才会有用。

答案 2 :(得分:3)

  • 在Delphi 2007(及更早版本)中,字符串是一个Ansi字符串,也就是说,每个字符占用1个字节的内存。

  • 在Delphi 2009(及更高版本)中,字符串是一个Unicode字符串,即每个字符占用2个字节的内存。

AFAIK,没有办法让Delphi 2009+ TStringList对象使用Ansi字符串。你真的使用TStringList的任何功能吗?如果没有,您可以使用字符串数组。

然后,您自然可以选择

type
  TAnsiStringArray = array of AnsiString;
  // or
  TUnicodeStringArray = array of string; // In Delphi 2009+, 
                                         // string = UnicodeString

答案 3 :(得分:3)

通过评论阅读,听起来你需要将数据从Delphi中提取出来并放入数据库中。

从那里很容易将器官捐献者与接收者匹配*)

SELECT pw.* FROM patients_waiting pw
INNER JOIN organs_available oa ON (pw.bloodtype = oa.bloodtype) 
                              AND (pw.tissuetype = oa.tissuetype)
                              AND (pw.organ_needed = oa.organ_offered)
WHERE oa.id = '15484'

如果您想看到可能与新器官供体15484匹配的患者。

在记忆中,你只处理少数匹配的病人。

*)简化了所有认可,但仍然。

答案 4 :(得分:1)

除了Andreas的帖子:

在Delphi 2009之前,字符串标头占用了8个字节。从Delphi 2009开始,字符串头需要12个字节。因此,每个唯一字符串比以前使用4个字节,+每个字符占用内存两倍的事实。

另外,从Delphi 2010开始我相信,TObject开始使用8个字节而不是4个。因此,对于delphi创建的每个单个对象,delphi现在使用4个字节。添加了这4个字节以支持我相信的TMonitor类。

如果你迫切需要节省内存,这里有一个小技巧可以帮助你,如果你有很多字符串值重复它们。

var
  uUniqueStrings : TStringList;

function ReduceStringMemory(const S : String) : string;
var idx : Integer;
begin
  if not uUniqueStrings.Find(S, idx) then
    idx := uUniqueStrings.Add(S);

  Result := uUniqueStrings[idx]
end;

请注意,如果您有很多重复的字符串值,这将有所帮助。例如,此代码在我的系统上少用150mb。

var sl : TStringList;
  I: Integer;
begin
  sl := TStringList.Create;
  try
    for I := 0 to 5000000 do
      sl.Add(ReduceStringMemory(StringOfChar('A',5)));every
  finally
    sl.Free;
  end;
end;

答案 5 :(得分:1)

我还在我的程序中读了很多字符串,这些字符串可以接近几GB用于大文件。

没有等待64位XE2,这里有一个可能对你有帮助的想法:

我发现在字符串列表中存储单个字符串在内存方面要缓慢且浪费。我最终阻止了这些字符串。我的输入文件有逻辑记录,可能包含5到100行。因此,我存储每条记录,而不是将每一行存储在字符串列表中。处理记录以找到我需要的行所需的处理时间非常少,因此我可以这样做。

如果你没有逻辑记录,你可能只想选择一个阻塞大小,并将每个(比如说)10或100个字符串存储为一个字符串(用分隔符分隔它们)。

另一种选择是将它们存储在快速有效的磁盘文件中。我推荐的是Synopse Big Table的开源Arnaud Bouchez

答案 6 :(得分:0)

我建议您尝试使用jedi类库(JCL)类TAnsiStringList,它类似于2007年来自Delphi的TStringList,它由AnsiStrings组成。

即便如此,正如其他人所提到的,XE将使用比delphi 2007更多的内存。

我真的没有看到将巨型平面文件的全文加载到字符串列表中的价值。其他人提出了一个大的方法,如Arnaud Bouchez的那个,或者使用SqLite,或类似的东西,我同意他们。

我认为您还可以编写一个简单的类来将您拥有的整个文件加载到内存中,并提供一种向逐行内存的ansichar缓冲区添加逐行对象链接的方法。

答案 7 :(得分:0)

从Delphi 2009开始,不仅字符串而且每个TObject的大小都增加了一倍。 (见Why Has the Size of TObject Doubled In Delphi 2009?)。但如果只有85,000个对象,这就无法解释这种增加。只有当这些对象包含许多嵌套对象时,它们的大小才能成为内存使用的相关部分。

答案 8 :(得分:0)

列表中是否有许多重复的字符串?也许尝试只存储唯一的字符串将有助于减少内存大小。看我的问题 about a string pool可能(但可能太简单)的答案。

答案 9 :(得分:0)

你确定你没有受到记忆破坏的情况吗?

请务必使用最新的FastMM(目前为4.97),然后查看包含内存映射表单的UsageTrackerDemo演示,其中显示了Delphi内存的实际用法。

最后看一下VMMap,它会向您展示如何使用您的进程内存。