代码之家  ›  专栏  ›  技术社区  ›  Jorge Córdoba

在Delphi中优化类大小。有“打包课程”之类的吗?

  •  4
  • Jorge Córdoba  · 技术社区  · 17 年前

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

    问题是,课程本身很小,但它们没有占用我所期望的空间。例如,如果我有

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

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

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

    在课堂上有什么方法可以做到同样的事情吗?(也许有一些关于如何正确重写NewInstance类方法的教程?)

    编辑: 好吧,解释一下我在做什么…

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

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

    这些对象会被传递并被强制转换为多个对象,而处理程序不知道它们得到了什么。例如,我有一个类,它接收一个滴度列表,并执行如下操作:

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

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

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

    这样,每个处理程序处理列表的方式就不同了…

    关于如何获得类的总大小,我使用了一个修改过的内存管理器,以便跟踪“实际”内存管理器为类消耗了多少内存。这样的话,我很有信心实例不会被打包。

    最后在Delphi7中。我尝试使用$A1预编译器指令,但没有运气,字段会以任何方式对齐,我可能有数百万个实例作为最坏的情况,因此保存6个字节可能会导致多个MB被保存。

    9 回复  |  直到 17 年前
        1
  •  10
  •   Gerry Coll    17 年前

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

    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;
    
        2
  •  4
  •   ChrisLively    17 年前

    为什么不从打包的记录开始呢?它会忽略掉从Tobject下降引起的开销(轻微的)。

        3
  •  4
  •   Ken White    17 年前

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

        4
  •  4
  •   Kenneth Cochran    17 年前

    当然。您可以打包集合、数组、记录、对象和文件类型。请注意,使用packed会导致访问数据的速度减慢,并可能导致一些类型兼容性问题。

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

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

    更新:

    我在Delphi6上也试过。它编译成功。如果打包的类不能在Delphi7中编译,那么您可能发现了一个bug。如果它是一个bug,那么它不太可能用embarcadero来解决它,除非它仍然出现在delphi的最新版本中,而事实似乎并非如此。

        5
  •  3
  •   Marco van de Voort    17 年前

    也许有点离题,但我以前在一些ORM框架中遇到过这种情况(2006年以前,没有记录)。假设“阶级”的东西是用石头砌成的:

    提示和提示:

    1. 我解决的打包问题是为字段设置getter和setter,并将它们存储在类的字节数组中。甚至可能被咬了。如果setters/getter是不可输入的(那么对我来说不是一个选项,d6),它甚至可能相当便宜。
    2. 尝试通过自己初始化内存块、设置vmt并对其调用构造函数来获取堆分配(管理开销和可宽延空间)开销。IIRC堆开销为8字节,旧堆管理器的分配粒度为8字节,fastmm为16字节。如果根据大小对类进行排序,则可以使用位图作为分配结构。
    3. 如果你是 尤其 邪恶,记住指针有2或3位的松弛度。我使用这些位作为非常常用的分配类型的标识,将堆保留的4字节保存到存储大小。
    4. 注意你的索引。如果你有很多对象(我有大约600万个),你也必须小心你的索引类型。(请不要列出电话号码)
    5. 始终将未混淆的内容保存在ifdef下,以便于调试测试(*)
    6. 不要将字符串用作键。必要时散列。规范化结构不仅有利于数据库

    (*)我后来在64位fpc下重新编译了“clean”版本,它在一些较小的sizeof(pointer())之后工作,尽管存在点1和点2的缺点。

        6
  •  2
  •   Jim McKeeth    17 年前

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

    您需要进行更多的工程设计,手动排列所有内容,但是通过使用getter和setter,行为在类外是透明的。您可以非常接近编译器以这种方式打包的结果。

        7
  •  2
  •   Ryan VanIderstine    17 年前

    如果您将拥有非常多的实例,并且希望避免与单个分配相关的开销,那么您可以使用打包的记录并从类本身的外部维护它们,例如通过一个或多个大型数组分配。

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

    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;
    
        8
  •  1
  •   Jim McKeeth    17 年前

    仅供参考,如果这是一个记录,那么它将是8个字节,6个字节作为压缩记录。因此,您将看到类指针的4字节开销(假设您在DelphiPre-2009中),如果打包了,就有可能回收2字节。

        9
  •  1
  •   Andreas Hausladen    17 年前

    我希望它使用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-(Delphi5)或$A1(更新版本)不起作用。Delphi7和Delphi2009都没有。

    btw:在Delphi2009中,“thread.monitor”还有4个字节,将类的总大小增加到 十六 字节。