6

我正在做一个模拟程序。

程序首先要做的事情之一是读入一个巨大的文件(28 mb,大约 79'000 行),解析每一行(大约 150 个字段),为对象创建一个类,并将其添加到 TStringList。

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

我正在使用 Delphi 2007,该程序使用了大量内存,但运行正常。我升级到 Delphi XE,并将程序迁移过来,现在它使用了更多的内存,最终在运行中途内存不足。

所以在 Delphi 2007 中,在读取初始文件后最终会使用 1.4 gigs,这显然是一个巨大的数量,但在 XE 中,它最终会使用几乎 1.8 gigs,这确实是巨大的,导致用完并获得错误

所以我的问题是

  1. 为什么它使用这么多内存?
  2. 为什么它在 XE 中使用的内存比 2007 多得多?
  3. 我能做些什么呢?我无法更改文件的大小或长度,我确实需要为每一行创建一个对象并将其存储在某处

谢谢

4

10 回答 10

10

只有一个可以节省内存的想法。

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

例如,我们几乎可以立即浏览大型日志文件:我们对日志文件内容进行内存映射,然后快速解析它以在内存中创建有用信息的索引,然后动态读取内容。读取期间不创建字符串。只有指向每一行开头的指针,动态数组包含所需的索引。调用TStringList.LoadFromFile肯定会慢得多并且消耗内存。

代码在这里- 查看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;

我们使用完全相同的技巧来解析 JSON 内容最快的 XML 访问库使用这种混合方法。

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

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

于 2011-08-25T17:47:11.740 回答
6

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

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

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

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

这将降低您的内存使用量和加载时间,但只有在加载后不需要所有(或大部分)对象时才有用。

于 2011-08-25T16:35:17.530 回答
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
于 2011-08-25T16:08:08.137 回答
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 匹配的患者。

在记忆中,您只处理少数匹配的患者。

*) 简化得面目全非,但仍然如此。

于 2011-08-25T17:45:07.403 回答
1

除了安德烈亚斯的帖子:

在 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;
于 2011-08-25T17:03:34.553 回答
1

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

无需等待 64 位 XE2,这里有一个可能对您有所帮助的想法:

我发现将单个字符串存储在字符串列表中既慢又浪费内存。我最终把琴弦挡在了一起。我的输入文件有逻辑记录,可能包含 5 到 100 行。因此,我没有将每一行存储在字符串列表中,而是存储每条记录。处理一条记录以查找我需要的行会为我的处理增加很少的时间,所以这对我来说是可能的。

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

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

于 2011-08-25T17:31:10.280 回答
0

我是否可以建议您尝试使用绝地类库 (JCL) 类 TAnsiStringList,它类似于 Delphi 2007 中的 TStringList,因为它是由 AnsiStrings 组成的。

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

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

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

于 2011-08-26T04:14:51.710 回答
0

从 Delphi 2009 开始,不仅字符串而且每个 TObject 的大小都翻了一番。(请参阅为什么在 Delphi 2009 中 TObject 的大小翻了一番?)。但是,如果只有 85,000 个物体,这并不能解释这种增加。只有当这些对象包含许多嵌套对象时,它们的大小才可能是内存使用的相关部分。

于 2011-08-26T05:49:02.000 回答
0

您的列表中有很多重复的字符串吗?也许尝试只存储唯一的字符串将有助于减少内存大小。请参阅我 的关于字符串池的问题以获取可能(但可能太简单)的答案。

于 2011-08-26T06:56:38.393 回答
0

你确定你没有遇到内存碎片的情况吗?

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

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

于 2011-08-26T08:20:33.403 回答