Delphi 高容量元素和列表以及;“奇怪”;分配存储器

Delphi 高容量元素和列表以及;“奇怪”;分配存储器,delphi,memory-management,delphi-xe2,Delphi,Memory Management,Delphi Xe2,最近几天,我在处理列表中大量的元素时,看到了一些关于Delphi内存分配的奇怪现象 我的电脑有12GB的内存;当我以64位模式运行我的应用程序(将数据存储在列表、大容量元素中)时,我观察到我的计算机使用的内存缓慢上升10 GB,并在短时间后下降,直到使用率下降到几乎8 MB(在内存中启动时,应用程序的正常大小)。 我想:如果我在内存中存储数据,所用内存的大小不应该相同吗?因为当大量数据存储在内存中且未清除时,为什么内存使用率会从8MB下降到10GB,然后又回落到8MB 这是正常的吗?谢谢你的建议

最近几天,我在处理列表中大量的元素时,看到了一些关于Delphi内存分配的奇怪现象

我的电脑有12GB的内存;当我以64位模式运行我的应用程序(将数据存储在列表、大容量元素中)时,我观察到我的计算机使用的内存缓慢上升10 GB,并在短时间后下降,直到使用率下降到几乎8 MB(在内存中启动时,应用程序的正常大小)。 我想:如果我在内存中存储数据,所用内存的大小不应该相同吗?因为当大量数据存储在内存中且未清除时,为什么内存使用率会从8MB下降到10GB,然后又回落到8MB

这是正常的吗?谢谢你的建议

更新

我报告了完整的代码,在哪里可以重现我以前说过的内容

program Project1;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  PsApi, windows, System.SysUtils, System.Generics.Collections;

type
  TMyRecord = record
    x: Integer;
    y: Integer;
  end;
  TMyArr = array of TMyRecord;
  TMyList = TList<TMyArr>;

function CurrentMemoryUsage: Cardinal;
var
  pmc: TProcessMemoryCounters;
begin
  pmc.cb := SizeOf(pmc);
  if GetProcessMemoryInfo(GetCurrentProcess, @pmc, SizeOf(pmc)) then
    Result := pmc.WorkingSetSize
  else
    RaiseLastOSError;
end;

var
  MyArr: TMyArr;
  MyList: TMyList;
  iIndex1, iIndex2: Integer;
  iMax: Int64;
begin
  try
    { TODO -oUser -cConsole Main : Insert code here }
    iMax := Low(Int64);
    Writeln(FormatFloat('Memory used before to create list: ,.# K',
      CurrentMemoryUsage / 1024));
    MyList := TMyList.Create;
    try
      Writeln(FormatFloat('Memory used before load item in list: ,.# K',
        CurrentMemoryUsage / 1024));
      for iIndex1 := 0 to 20000000 do
      begin
        SetLength(MyArr, 100);
        for iIndex2 := 0 to High(MyArr) do
        with MyArr[iIndex2] do
        begin
          x := iIndex2 + 1;
          y := iIndex2 - 1;
        end;
        MyList.Add(MyArr);
        if CurrentMemoryUsage > iMax then
          iMax := CurrentMemoryUsage;
      end;
      Writeln(FormatFloat
        ('Memory used (max) during to load item in list: ,.# K', iMax / 1024));
      Writeln(FormatFloat('Memory used to end load item in list: ,.# K',
        CurrentMemoryUsage / 1024));
    finally
      MyList.Free;
    end;
    Writeln(FormatFloat('Memory used after destroyed list: ,.# K',
      CurrentMemoryUsage / 1024));
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;

end.
在这种情况下,告诉“在加载列表中的项目”时,我得到了几乎4 GB的内存,但在完成加载列表中的项目后,使用的内存几乎是2-3 GB。 我试着做了很多模拟,这个值在变化,不是一个常数,而是在一般方面这个值。
我再说一遍,这对我来说并不重要。只是为了了解更多关于内存分配的信息

从您对评论的回答中,您将数百万条条目放入列表中。您尚未指定列表的类型,但如果它是TList或Decentant,它将自动增加列表以适应

当增加列表时,它会重新分配一个新的更大的数组,并将旧的列表压缩到其中,如果在一个紧密循环中添加数百万个条目,则每当达到容量时(至少对于TList),就会重新分配内部列表

旧列表的内存没有立即释放/可重用,我相信这是您使用大量内存的原因

如果您知道列表的大小,您应该使用Capacity属性预先调整数组的大小,如果不在循环中添加一些额外的代码来跟踪列表的当前计数,并且如果接近容量增加,则容量将比它将自动执行的25%的值大得多。这将大大减少内存分配量,从而降低内存使用率(性能也会更好)

希望有帮助

更新
修改了答案以删除不正确的列表大小调整信息,正如Kohi指出的那样。

@Dampsquid是正确的,但TList子代不会以16个元素的固定增量增长,并且已经很长时间没有这样做了(D3?)。一旦您得到64个元素,每次调整大小时它都会增长25%。除非您使用的是Delphi的免费版本,否则您可以自己看到这一点。按住Ctrl键单击TList声明并四处查看。Add()调用Grow(),如下所示:

  procedure TList.Grow;
  var
    Delta: Integer;
  begin
    if FCapacity > 64 then
      Delta := FCapacity div 4
    else
      if FCapacity > 8 then
        Delta := 16
      else
        Delta := 4;
    SetCapacity(FCapacity + Delta);
  end;
即使你猜测一下最终的容量,你的境况也会好得多。如果您知道将要使用数百万个条目,请先将容量设置为一百万。这样,你就可以跳过30多个调整大小的操作,而这30多个操作是将你的列表增加到100万的。我会倾向于以另一种方式犯错,并猜测你认为你需要的最大数字。每个额外条目“浪费”8个字节,但避免了一系列的重新分配和复制操作,因此出错几百万只会花费16MB。当您使用GB内存时,这算不了什么


(很抱歉回答这个问题,我没有足够的要点来评论)。

具体问题是什么?你有什么问题吗?并具体说明您正在测量的内容。“我公司的内存增加到10GB”没有任何意义。你必须准确。对不起,尽量解释得更清楚些。我写了一个简单的程序,加载内存统计数据并将其保存在列表中。这个列表包含很多元素(大约一百万个)。没什么特别的问题。只是我观察到,在处理数据的过程中,我的应用程序占用的内存少于8MB,慢慢地占用更多的内存,直到接近10GB。之后,当进程终止(不是程序)时,总是缓慢地从10GB的ram恢复到8MB。当到达8MB时,程序正确终止。一般来说没有问题,但我想知道这是否正常。我想如果我需要(例如:1 GB或10 GB)存储所有数据,因为之后不保留此值,而不返回8 MB?从某种意义上说,数据是正确存储的。为什么delphy会占用所有内存(100%),然后将其释放到8MB?只是为了更好地理解,但这对我来说当然不是一个真正的问题。@DavidHeffernan根据实际销毁这些对象的代码,对于这种“逐渐”减少所需的时间还没有任何说明。释放这数百万个对象中的一个可能需要一秒钟的时间,但这一百万个对象加在一起会乘以这一秒,并且会随着时间的推移而发生。在不适当销毁这些对象的情况下杀死进程实际上会立即将其置为0,但这并不意味着您应该强制杀死进程。正确地销毁这些对象可能会很费时,但这并不意味着它总是如此,所需的时间会有所不同。@Jerry只有你在谈论降到0。没有其他人是。无可否认,我们不可能知道这个问题到底是关于什么的。+1,既因为我同意(虽然不确定它是否回答了这个问题),也因为你展示了666……好的,谢谢,读得更好,你的解释我已经理解了它的原因。再次感谢。对于您发布的代码,是的,我希望看到您看到的内容,因此我认为是的,这是正常的行为,但出于我所述的原因,我不会为20 mil条目列表重新提出这种方法。
  procedure TList.Grow;
  var
    Delta: Integer;
  begin
    if FCapacity > 64 then
      Delta := FCapacity div 4
    else
      if FCapacity > 8 then
        Delta := 16
      else
        Delta := 4;
    SetCapacity(FCapacity + Delta);
  end;