在Delphi中优化类大小。有类似“打包课”的东西吗?

时间:2009-05-06 18:46:43

标签: delphi optimization memory

我正在尝试优化我的Delphi类的大小,以便它们占用尽可能少的内存,因为我正在创建大量的内存。

问题是,这些课程本身很小但是他们没有占据我期待的空间。例如,如果我有

type MyClass = class
  private
    mMember1 : integer;
    mMember2 : boolean;
    mMember3 : byte;
end;

我希望它使用6个字节,但是,由于对齐,它最终使用12个字节,这是布尔值使用4个字节而不是1个字节......对于字节字段也是如此...

对于记录,您可以使用{$ A1}指令或将其声明为打包记录,以使其仅消耗所需的内存。

有没有办法和班级做同样的事情? (也许有关如何正确覆盖NewInstance类方法的一些教程?)

编辑:好的,关于我正在做什么的一点解释......

首先,实际类大小类似于40个字节,包括VMT占用的空间和接口指针。

所有类都继承自大小为8字节的基本RefCounting类(一个整数FRefCount和一些允许引用计数的方法),它们必须支持接口(因此根本不使用打包记录)。

这些对象被传递并被投射到几个东西,而不知道他们得到了什么。例如,我有一个接收TItems列表的类,并执行类似的操作:

if Supports(List[i], IValuable, IValInstance) then
  Eval(IValInstance.Value);

然后另一个处理程序可以检查其他接口

If Supports(List[i], IStringObject, IStringInstance) then
  Compose(IStringInstance.Value)

这样每个处理程序对List的处理方式都不同......

关于如何获得类的总大小我正在使用修改后的内存管理器,以便我可以跟踪“真实”内存管理器用于该类的内存量。通过这种方式,我非常自信地没有被包装。

最后这是在Delphi 7中。我试图使用{$ A1}预编译器指令没有运气,字段以任何方式对齐,并且我可能有几百万个实例作为最坏的情况,因此节省6个字节可以导致几个MB被保存。

9 个答案:

答案 0 :(得分:10)

您可以将打包记录用作对象的字段:

type
  TMyRecord = packed record
    Member1 : integer;
    Member2 : boolean;
    Member3 : byte;
  end;

  TMyClass = class
  private
    FData : TMyRecord;
   function GetMember1 : Integer;
  public
    property Member1 : Integer read GetMember1;
    // Later versions of Delphi allow "read FData.Member1;", not sure when from
  end;

function TMyClass.GetMember1 : integer;
begin
  result := FData.Member1;
end;

答案 1 :(得分:4)

为什么不直接使用打包记录?它会省去由TObject ......降序引起的开销(轻微)。

答案 2 :(得分:4)

绝对。您可以打包集,数组,记录,对象和文件类型。请注意,使用packed会在访问数据时导致速度变慢,并且可能会导致某些类型兼容性问题。

我在Delphi 2006中试过这个。编辑器的语法检查将其标记为错误,但编译得很好。

根据Delphi文档,$ A开关适用于类类型和记录类型。

<强>更新

我也在Delphi 6中尝试过这个。它编译成功。如果打包类不能在Delphi 7中编译,您可能发现了一个错误。如果它是一个bug,它不太可能的Embarcadero将对它做任何事情,除非它仍然出现在Delphi的最新版本中,似乎并非如此。

答案 3 :(得分:4)

如果你担心几个字节(你提到6对12),你根本不应该使用一个类。改为使用记录。然后您可以使用包装来消除对齐浪费;但是,请准备好降低性能,因为默认的“非打包”对齐设置为CPU的最快访问。

答案 4 :(得分:3)

也许有点offtopic,但我之前(在D2006之前,所以没有记录)对于某些ORM框架我一直在努力。假设“阶级”的东西是一成不变的:

提示和提示:

  1. 我通过为字段设置getter和setter来处理的打包问题,将它们存储在类的字节数组中。甚至可以进行bitpacked。如果setter / getters是可以接受的(那么对我来说不是一个选项,D6)它甚至可以相当便宜。
  2. 尝试通过自己初始化一块内存,设置VMT并在其上调用构造函数来获取堆分配(管理开销和松弛空间)开销。 IIRC堆开销是8个字节,旧堆的分配粒度是8字节,而fastmm是16字节。如果根据大小对类进行排序,则可以使用位图作为分配结构
  3. 如果您特别邪恶,请记住指针有2或3位松弛。我使用这些位作为极其常用的分配类型的标识,将堆保留的4字节保存到存储大小。
  4. 注意你的索引。如果你获得了很多对象(我有大约600万个),你也必须小心你的索引类型。 (请不要tstringlist)
  5. 始终将非混淆的内容保留在ifdef下,以便于调试(*)
  6. 从不使用字符串作为键。如果必要的哈希。规范化结构不仅有利于数据库
  7. (*)我后来在64位FPC下重新编译了“干净”的版本,并且它在一些小的sizeof(指针())之后工作,尽管点1和2的uglinesses

答案 5 :(得分:2)

手动打包数据。取每4个字节并将它们放在一个基数中。如果你有两个短的长度不是4的长度,那么把它们放入一个短的字符串中,然后只读出每个的部分。

您需要更多的工程设计,手动排列所有内容,但通过使用getter和setter,行为将在课堂外透明。你可以得到与编译器打包方式相同的结果。

答案 6 :(得分:2)

如果您要拥有大量实例并希望避免与单个分配相关的开销,则可以使用打包记录并从类本身外部维护它们,例如通过一个或多个大型分配阵列。

然后,在类中,您只能存储一个或两个字段以索引到堆和偏移量。如果只能使用一个大的内存块,则可以将其减少到只有偏移量。

TPackedRecord = packed record ... end;
PPackedRecord = ^TPackedRecord;
TPackedRecordHeap = class
  ...
  function  Add: PPackedRecord;
  procedure Release( entry: PPackedRecord );
end;

TUsableClass = class
private
  heap: TPackedRecordHeap;
  data: PPackedRecord;
public
  constructor Create( heap: TPackedRecordHeap );
  ...
end;

答案 7 :(得分:1)

仅供参考,如果这是一个记录,它将是8个字节,6个字节作为打包记录。因此,您正在考虑类指针的4字节开销(假设您在2009年之前的Delphi中),如果它被打包,可能会回收2个字节。

答案 8 :(得分:1)

  

我希望它使用6个字节,但由于对齐,它最终使用12个字节

即使你写“TMyClass = class end;”该类将继承自具有虚方法的TObject。

这样做

  4 Bytes (VMT)
+ 4 Bytes (member1: Integer)
+ 1 Byte  (member2: Boolean)
+ 1 Byte  (member3: Byte);
+ 2 Bytes (alignment)
---------
 12 Bytes

因此,如果您禁用了对齐,则只会获得2个字节。

通过对数据类型大小(在您提到的较大类中)排序字段可以消除一些对齐孔。并且$ A-(Delphi 5)或$ A1(更新)不起作用。在Delphi 7和Delphi 2009中都没有。

BTW:在Delphi 2009中,“Thread.Monitor”还有4个字节,将总类大小增加到 16 字节。