我正在尝试优化我的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被保存。
答案 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框架我一直在努力。假设“阶级”的东西是一成不变的:
提示和提示:
(*)我后来在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 字节。