【问题标题】:Faster way of initializing arrays in Delphi在 Delphi 中初始化数组的更快方法
【发布时间】:2010-05-12 09:21:56
【问题描述】:

我正在尝试在我的 Delphi 应用程序中充分发挥性能,现在我找到了一个适用于动态数组的程序。其中最慢的一行是

SetLength(Result, Len);

用于初始化动态数组。当我查看 SetLength 过程的代码时,我发现它远非最佳。调用顺序如下:

_DynArraySetLength -> DynArraySetLength

DynArraySetLength 获取数组长度(初始化为零),然后使用 ReallocMem,这对于初始化也是不必要的。

我一直在做 SetLength 来初始化动态数组。也许我错过了什么?有没有更快的方法来做到这一点?

编辑:描述主要算法会占用大量空间,实际上是不必要的,因为它试图优化其中的一小部分。一般而言,它是车辆路线问题 (http://en.wikipedia.org/wiki/Vehicle_routing_problem) 的一个变种。我确实需要数以千计的分配,因为我必须保留所有数据,并将其分开保存。如果我能在这里想到一些巧妙的数据结构,可能会有所帮助,但我能想到的任何东西都会大大增加代码的复杂性。 基本上我已经在算法层面上做了我能做的一切,所以现在我试图从底层的东西中得到我能做的一切。所以这是一个相当狭窄的问题:是否有可能增加这个特定的调用。而且我认为要做到这一点,我需要根据 SetLength 代码编写自己的初始化函数。并使其内联。

【问题讨论】:

  • SetLength() 用于初始化和设置数组的长度。所以,我看不到如何优化它 - 拆分这两个功能。这真的是一个问题吗?您应该只在初始化期间执行它吗?或者你在一个循环中执行了很多次?
  • 一个长度为零的数组由一个 nil 指针表示,FWIW - 实际上将 nil 分配给一个动态数组位置等价于 SetLength(arr, 0)。如果SetLength 太慢,你可能调用它太频繁了;调用一次,为最大尺寸设置足够大的尺寸,然后独立跟踪长度,就像 Andreas 在他的回答中所说的那样。
  • 您是否调用过执行无数次 SetLength(Result, Len) 的函数,或者调用过无数次调用执行一次 SetLength(Result, Len) 的函数?在第一种情况下,请检查下面的 Andreas 答案。在第二种情况下,它会更加棘手。
  • 您能描述一下基本算法吗,特别提到何时请求内存分配?
  • @LeGEC 我有无数次调用一个函数,该函数执行一次 SetLength(Result, Len)。

标签: performance delphi


【解决方案1】:

这更像是一个评论,但由于在 cmets 中发布大块代码并不漂亮,所以我将它发布在这里。

有时,如果您不知道最终会得到多少个元素,那么编写这样的代码可能很诱人:

var
  Arr: array of cardinal;

procedure AddElement(const NewElement: cardinal);
begin
  SetLength(Arr, length(Arr) + 1);
  Arr[high(Arr)] := NewElement;
end;

这很糟糕,因为每次添加新元素时都需要重新分配内存。更好的方法(如果可能)是找到元素数量的上限,例如 MAX_ELEMENTS = 100000;然后初始设置长度:

SetLength(Arr, MAX_ELEMENTS);

然后你创建一个变量像

var
  ActualNumberOfElements: cardinal = 0;

然后写

procedure AddElement(const NewElement: cardinal);
begin
  Arr[ActualNumberOfElements] := NewElement;
  inc(ActualNumberOfElements);
end;

填充完数组后,只需截断它:

SetLength(Arr, ActualNumberOfElements);

【讨论】:

  • 或者尝试逐步增加:if Length(arr)
  • 问题不在于我调用 SetLength 来调整数组的大小。我只调用 SetLength 一次,因为我知道它有多大。我不做重新分配。但是使用 SetLength 初始化数组的代码是该方法中最慢的部分。
  • @Max:但事实并非如此。在上面的评论中,对于 leGEC,您声明包含 Setlength 的函数被称为无数次。这与直接调用Setlength 无数次完全一样。
【解决方案2】:

如果您只调用一次并且需要很长时间,那么您请求的 RAM 超出了可用内存,导致操作系统换出其他内容以尝试为您腾出空间。修复:添加更多 RAM,使用更小的数组。

如果您在循环中调用它,那么“realloc”就是问题所在。每次调用它时,都会一点一点地增加数组的长度,Delphi 会重新分配整个数组,然后将旧数组中的所有内容复制到新数组中。效率不高。

【讨论】:

  • VCL 中的其他内容,如 TList 和 TMemoryStream,通过分配比请求更多的内存来减少它们的重新分配,然后在重新分配之前等待该内存填满。类似于 Andreas 所描述的。
【解决方案3】:

马克斯写道:

我有无数次调用函数 哪一次 SetLength(Result, 伦)。

检查你如何使用你的函数:你真的需要对这个分配函数进行无数次不同的调用吗?你能像弗朗索瓦建议的那样重新设计你的代码以减少调用次数吗?

如果你真的需要对你的函数进行无数次不同的调用,并且需要加快速度,我认为你需要保留动态数组并使用不同的结构。

但在这样做之前,进入一个完整的调试地狱,我会强烈加入弗朗索瓦的建议,并尝试重新设计代码。

也许你能告诉我们更多关于你的算法的信息?是关于处理图形 3D 结构吗?

[编辑] 好的,如果是关于解决 NP 完全问题,我会尽量避免分配。

大多数时候,您可以为数组/堆栈/结构的大小设置上限 - 例如:#cities、#vehicles + #drivers、#roads * #cities ...

对于这些部分,我建议分配一次最大可能的数组,并手动处理您仅使用前 n 行等的事实。

考虑到它可以节省之后的计算时间,即使在 n^2 或 n^3 中分配结构也是可以接受的。

优化 SetLength :您的建议是有道理的。

但是,如果动态数组非常适合编写 Delphi 代码 - 主要是因为它们的“自动构造函数”语义 - 恕我直言,它们不太适合高性能计算 - 它严重依赖 RTTI,引用计数可以为您提供时不时有惊喜...
您的建议是动态数组语义的变化。试试看“改变你的数据类型”的解决方案是否真的那么不可能编写和调试。

【讨论】:

  • 我已经对我的问题进行了一些澄清
【解决方案4】:

"过早的优化是万恶之源。"
我怀疑您是否必须关心一生一次的初始化...
那是除非您将其称为无数次,否则我建议您重新设计代码。

【讨论】:

    【解决方案5】:

    与其调用包含SetLength 的函数无数次,不如预先分配存储空间,并将预先分配的数组传递给函数。一种这样的方式是这样的:

    const
      NUM_ARRAYS = 1000;
      ARRAY_SIZE = 1000;
    type
      TMyArray = array [0..ARRAY_SIZE] of Integer;
    var
      MyStorage: array[0..NUM_ARRAYS] of TMyArray;
    
    procedure DoWork(var AArray: TMyArray);
    begin
      Blah;
    end;
    
    procedure MainLoop;
    var
      i: Integer;
    begin
      for i := 0 to High(MyStorage) do
      begin
        DoWork(MyStorage[i]);
      end;
    end;
    

    这将比在内部循环中调用 SetLength 快显着。需要注意的主要事情是为硬件使用过多的内存。

    【讨论】:

    • 我不能这样做,因为数组是一个记录的成员,它是另一个数组的一个元素。而且整个结构在不断变化。
    【解决方案6】:

    SetLength 最慢的部分不是“不必要的调用”之类的 - 它是由内存管理器执行的内存分配(在您的情况下,ReallocMem 将是简单的 GetMem,因为源指针为零)。

    那么,您使用什么内存管理器?您是否有机会在没有 FastMM 的情况下使用 D7?

    尝试安装 FastMM 看看是否有帮助。

    【讨论】:

    • 没有。我正在使用 D2007。使用 FastMM。
    猜你喜欢
    • 2022-01-13
    • 1970-01-01
    • 1970-01-01
    • 2018-04-05
    • 2015-10-18
    • 2013-12-12
    • 2011-03-13
    相关资源
    最近更新 更多