【问题标题】:What bookkeeping data does a Delphi dynamic array contain?Delphi 动态数组包含哪些簿记数据?
【发布时间】:2010-12-26 14:18:14
【问题描述】:

这是一个检查内存分配的简单程序。使用任务管理器检查前后值表明每个动态数组在 size = 1 时占用 20 个字节的内存。元素大小为 4,这意味着簿记数据的开销为 16 个字节。

通过查看 system.pas,我可以找到 -4 字节的数组长度字段和 -8 字节的引用计数,但我似乎找不到对其他 8 个的任何引用。任何人都知道他们怎么办?

示例程序:

program Project1;

{$APPTYPE CONSOLE}

type
   TDynArray = array of integer;
   TLotsOfArrays = array[1..1000000] of TDynArray;
   PLotsOfArrays = ^TLotsOfArrays;

procedure allocateArrays;
var
   arrays: PLotsOfArrays;
   i: integer;
begin
   new(arrays);
   for I := 1 to 1000000 do
      setLength(arrays^[i], 1);
end;

begin
  readln;
  allocateArrays;
  readln;
end.

【问题讨论】:

    标签: delphi data-structures overhead dynamic-arrays


    【解决方案1】:

    我也查看了 System.pas,并注意到 _DynArrayCopyRange 中的 GetMem 调用支持您的分析:

    分配大小 = 计数 * 元素大小 + 2 * Sizeof(Longint)

    。因此,您从任务管理器获得的数字可能不是很准确。您可以尝试 Pointer(someDynArray) := nil 并检查 FastMM 报告的内存泄漏大小以获得更可靠的数字。

    编辑:我做了一个小测试程序:

    program DynArrayLeak;
    
    {$APPTYPE CONSOLE}
    
    uses
      SysUtils;
    
    procedure Test;
    var
      arr: array of Integer;
      i: Integer;
    begin
      for i := 1 to 6 do
      begin
        SetLength(arr, i);
        Pointer(arr) := nil;
      end;
    end;
    
    begin
      ReportMemoryLeaksOnShutdown := True;
      Test;
    end.
    

    这会产生

     发生了意外的内存泄漏。意外的小块泄漏是:
    
      1 - 12 字节:未知 x 1
      13 - 20 字节:未知 x 2
      21 - 28 字节:未知 x 2
      29 - 36 字节:未知 x 1

    支持8字节开销理论。

    【讨论】:

    • 你是对的。它就在代码中。任务管理器非常准确,但它衡量的是应用程序从 Windows 分配的 RAM 量。所以真正的问题是,为什么 FastMM 从操作系统中获取的内存比它需要的多得多?也许是为了减少所需的内存请求总数,并减少碎片?
    • 任务管理器在诊断 Delphi 内存使用时不准确。除了动态数组本身的开销之外,它没有考虑 VCL 的内存管理器为其自身内部所需的额外开销。并且它没有考虑到内存管理器在“释放”时缓存和重用内存块,因为它们不会返回给操作系统。
    • 是的。我的意思是,任务管理器准确地测量它测量的内容,即应用程序从 Windows 请求的数量,而不一定是应用程序内部实际使用的数量。
    • 我记得 FastMM 使用了一种聪明的方法。它不会立即返回每个释放的内存块。这是合理的,因为通常会出现新的分配。可能它与数组分配有类似的方法。但我不确定。
    【解决方案2】:

    内存分配具有粒度以确保所有分配都对齐。这只是由此造成的败笔。

    【讨论】:

      【解决方案3】:

      更新... 我实际上去检查了代码(我以前应该这样做过),我得出了与 Ulrich 相同的结论,它没有存储任何类型信息,只是 2 个 Longint 开销,然后是 NbElements*ElementSize。
      而且,任务管理器对于这种度量并不准确。

      奇怪的是,如果您测量 dynarray 使用的内存,它会随着元素的大小非线性地增加:对于具有 2 或 3 个整数的记录,它的大小相同 (20),而 4 或 5 则为 28 ...遵循块大小的粒度。

      内存测量:

      // Return the total Memory used as reported by the Memory Manager
      function MemoryUsed: Cardinal;
      var
        MemMgrState: TMemoryManagerState;
        SmallBlockState: TSmallBlockTypeState;
      begin
        GetMemoryManagerState(MemMgrState);
        Result := MemMgrState.TotalAllocatedMediumBlockSize + MemMgrState.TotalAllocatedLargeBlockSize;
        for SmallBlockState in MemMgrState.SmallBlockTypeStates do begin
            Result := Result + SmallBlockState.UseableBlockSize * SmallBlockState.AllocatedBlockCount;
        end;
      end;
      

      【讨论】:

      • 我不这么认为。所有相关例程似乎都将类型信息作为单独的参数,编译器会跟踪它。
      • Re: 非线性:我猜这是内存管理器的产物,对于 dyn 数组没什么特别的。
      • 作为该线程的骑手,我在使用动态数组和 SetLength 分配内存时遇到了问题,如果您构建扩展列表(现在很明显)就会出现这种问题。我不想使用 TList 后代。解决此问题的一个简单方法是为动态数组设置“容量”,我想知道自己做 GetMem,然后使用 ABSOLUTE 将扩展数组修复在同一个“插槽”中,但是 yuk……我我很惊讶编译器不支持这个功能。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-24
      • 2013-01-19
      • 1970-01-01
      相关资源
      最近更新 更多