Delphi:在类和记录中存储数据,减少内存使用量

时间:2009-12-31 01:27:18

标签: class delphi oop memory

在应用程序运行时,我在内存中存储,读取和修改了大量数据。可以将数据与树进行比较,其中每个节点由有限数量的字符串和整数描述,并且具有相当多的子元素。 目前,数据是使用类/对象存储的,例如

TRootElement = class
  fName, fDescription: string;
  fPos: integer;
  /// etc
end;

fDocs: TObjectList; //list of TVariable = class(TRootElement)
fClasses: TObjectList; // list of TClass=class(TRootElement)

目前程序消耗的内存是不可接受的,因此我正在寻找限制它的解决方案。

我的问题是:如果我将基于记录的当前,OOP和基于对象的架构替换为,那么消耗会显着降低吗? 例如,一般记录可以包含:

TRootElement = record
  fType: TElemType; // enum: root, variable, class, etc ... 
  fName, fDesc: string; 
  // all the fields used by root elem and it's descendants there
end;

我应该用指向下一个/前一个元素的指针替换TList吗?因为我从来没有按索引访问列表元素,所以我总是在整个列表中循环,这应该不是很难...但是如果没有必要的话我想避免它。

谢谢! 米。

4 个答案:

答案 0 :(得分:14)

将类更改为记录将减少内存使用量,但随着类或记录中字段数量的增加,节省的重要性会降低。类和相应记录之间的大小差异恰好是四个字节,它占了一个类所持有的VMT pointer但是记录中没有。{3}}。当您考虑权衡时,这种差异通常可以忽略不计:为了节省四个字节,您放弃了继承,多态,数据隐藏和其他面向对象的功能。 (其中一些可能会通过Delphi的新“记录方法”来缓解,但如果你只有Delphi 2005,那么你还没有这个功能。)

事实上,如果这四个字节确实为您的程序带来了不同,那么您可能需要解决更大的问题。只需在树中添加另一个节点即可消除四字节的节省。使用足够大的数据集,无论您制作任何一个节点有多小都无关紧要,因为无论如何您都无法将它们全部保存在内存中。您需要调查某种缓存方案,因此只有一些节点保留在内存中,其余节点保存在其他位置,例如文件或数据库中。

如果用双链接的节点列表替换当前列表,您可能会看到内存使用增加,因为现在每个节点都在跟踪其下一个和前一个邻居,而在此之前TObjectList正在管理这一切。

答案 1 :(得分:2)

  

目前程序消耗的内存是不可接受的

不可接受的含义是什么?你有问题吗?什么是事实(对象的数量,对象的大小,已用的内存)?

如果你的程序有内存泄漏,你有没有检查过FastMM?如果不是这是你应该做的第一个想法。

如果您的列表经常增长,那么您可能会遇到内存碎片问题。使用列表的capacity属性(如果可能)。在这种情况下,链表可以提供帮助,但链表需要比TList更多的内存(如果使用合理的容量)。看到 How to monitor or visualize memory fragmentation of a delphi application了解有关如何检查的更多信息。

对于Delphi< = 2005,使用FastMM替换Borland内存管理器会很有帮助,这很容易。

至少,像Rob一样,我认为改变记录不会解决你的问题。

答案 2 :(得分:2)

与绝大多数IDE相比,加载所有仅10兆字节的PHP5元数据的内存增加实际上相当不错。

如果你真的值得努力,我会从字符串文字合并开始。

将所有字符串放在全局表(或字典)中,并指向所有字符串中的字符串。

您可以更进一步,因为PHP 5语言和库非常静态:将整个数据结构从动态转换为静态常量(使用记录),以及所有索引枚举类型。

您可以做的是使所有字符串资源字符串或字符串常量,并查看Delphi编译器是否可以为您进行文字合并。

我刚注意到你还加载了所有PHP5内容的文档。这占了相当多的记忆。 您可能希望将它们加载到压缩流中。

答案 3 :(得分:1)

如果您可以像Kornel所说的那样设置字符串限制,那真的很重要。 ansistring有一些内部开销,也有额外的开销。但是,即使未使用,也始终会分配shorttring。

如果你对内存非常紧张,那么为字符串做自己的分配是更明智的,特别是如果数据是相对不可变的。然后简单地分配一个大块,并将所有字符串放在那里,前缀为16位左右。

较少的低级技巧,例如简单地重复删除(某些)字符串也会节省大量存储空间。

请注意,Rob的记录与类的讨论只有在你设法以非常便宜的方式静态实例化内存中的类时,你可能不会这样做。这是因为你可以使用记录数组。否则,它始终是引用类型的事实导致堆栈头和-slack(fastmm,16字节粒度)

我建议不要使用tstringlist / tlist / tobjectlist,因为非常大的列表(数百万)中的插入删除可能很痛苦,因为删除/插入是O(n),插入中间意味着移动一半的数据。这会在20-100k到1M元素之间变得很痛苦,具体取决于您的访问模式。

使用tlist列表,而不是让每个tlist变得太大已经是一个很好的解决方法。

当我这样做时(对于OLAP集群,当2GB服务器内存仍为2000美元时),我甚至在指针中使用了对齐位来存储大小级别的分配。我不建议这样做: - )

当然,使用FPC进行64位也是一种选择。我有一个以上32位解决方案的核心服务器部分在一小时内以64位工作。