【问题标题】:Is there a faster TList implementation?有更快的 TList 实现吗?
【发布时间】:2010-04-06 12:21:10
【问题描述】:

我的应用程序大量使用 TList,所以我想知道是否有任何替代实现更快或针对特定用例进行优化。

我知道RtlVCLOptimize.pas 2.77,它优化了几个 TList 方法的实现。

但我想知道那里是否还有其他东西。我也不需要它是 TList 后代,我只需要 TList 功能,不管它是如何实现的。

鉴于 TList 提供的相当基本的功能,完全有可能没有太大的改进空间,但仍想验证这一点,因此提出了这个问题。

edit:在我的特定用例中,没有对列表进行排序。有很多列表,其中包含不同数量的元素。我确实用我自己的类替换了 TList 以记录添加/删除调用的数量和元素的数量。它报告(所有列表的总计):

ListAdd = 15766012; ListRemove = 10630000; ListCount = 5136012

我还可以找出单个列表中元素的最大数量。

我没有什么特别的问题,我只是想知道是否有办法让它变得更快,因为有了这些数字,即使是很小的改进也会加起来。

【问题讨论】:

  • 有多少项目,排序与否,您特别关心什么?即 10,000+ 行太慢?
  • 除了添加、删除和计数之外,您还使用其他方法吗?
  • 顺便说一句,您使用的是什么版本的 Delphi?
  • @ChrisThornton 大小分开通常插入和删除低索引项似乎很重要。

标签: delphi optimization


【解决方案1】:

我所知道的关于 TList 的最大瓶颈之一是大列表上的删除/提取。删除 item[0] 比删除 Item[Count-1] 慢很多,因为它会发生内存移动。

例如,在包含 65536 个元素的列表上:

while list.Count > 0 do List.Delete(0) //Takes 2 mins to complete

for I := List.Count-1 downto 0 do List.Delete(I) //Takes less than 1 sec

因此,如果您有一个包含数百万个元素的 TList,则删除低索引项在性能方面可能会很昂贵。另外,考虑到有一个未排序的列表会使在其中找到一个元素变得非常慢。 IndexOf 在大列表上非常慢。您可能需要考虑保持列表排序。

另外,考虑到您的项目数量可能非常大,您可能需要考虑使用 TList 的列表来存储您的元素,这将有助于减少我已经提到的删除/提取开销。

【讨论】:

    【解决方案2】:

    看起来你做了很多添加。我不知道分布了多少个列表,但如果您的单个列表变得非常大,您可能希望实现一个增长更快的列表。

    看看TList.Grow,当您尝试将一个项目添加到所有数组元素都在使用的列表中时会调用它,您会看到它增长了 25%。这是为了将内存使用降低到合理的水平。但是,如果您需要非常大的列表,请创建自己的后代类并覆盖 Grow,以便在第二行中,而不是 Delta := FCapacity div 4 它显示为 Delta := FCapacity。这使您的列表每次都增长两倍,这意味着更少的重新分配和更少的副本。

    但可能要杀死你的是所有那些 Remove 调用。 Remove 必须先找到该项目,然后才能将其删除,这涉及对 IndexOf 的调用,这是对整个数组的线性扫描。如果你有一个很大的列表并且你做了很多删除,那会影响你的表现。

    您将这些列表用于什么目的,尤其是大型列表?根据您对它们所做的工作,可能会有更好的数据结构来完成这项工作。

    【讨论】:

    • Overriding Grow 是一个不错的建议。或者,可以在创建列表时设置 List.Capacity,这也可以节省大量的 realloc。
    • 是的,这也是个好主意,尤其是如果您对列表最终将包含多少项目有一个很好的估计。
    • 相关想法:可能有机会将项目标记为已删除,然后重新使用它们而不是实际删除。仅当您不必花费大量时间搜索“打开”(标记为已删除)项目时才有用。
    • 今天TObjectList<TMyObject> 也同样适用吗?我没有看到Grow 函数,只有TList.GrowCheck。事实上,在 Delphi 10.1 Berlin 中,System.Generics.Collections 的第 4071 行,TList<T>.Grow 被注释掉了。
    • 使用枚举器for MyObject in MyList do 会比索引快吗? for X := 0 to MyList.Count-1 do EDIT 其实刚试过,使用枚举器迭代列表似乎更慢。
    【解决方案3】:

    您是否正在使用通知程序?如果没有,请制作您自己的 TList 实现。由于 Notify 过程,TList.Clear(在销毁时调用)是一个 O(n) 操作。 TList.Clear 方法调用 SetCount,而 SetCount 又为它包含的所有项目调用 Delete,因此将为每个删除的项目调用 Notify 过程。当您不需要重写 Notify 方法时,您可以调整 SetCount 过程以不调用 Delete。这可以为您节省 15.766.012 - 10.630.000 = 5.136.012 删除呼叫的时间。

    注意:您获得的性能提升永远不会像 Mason Wheeler 所建议的那样,通过对列表进行排序和优化删除过程所获得的性能提升那么大。除非您拥有的列表包含的项目数量很少,并且比较功能需要很多时间。

    【讨论】:

    • 好点,但值得注意的是,如果这是一个 TObjectList 而不仅仅是一个普通的 TList,则 Notify 必须存在。但是,如果不是,则很可能没有充分的理由使用 Notify。
    【解决方案4】:

    最快的数据结构通常根本不是数据结构,而是一个只在需要时提取数据的模拟,就像Virtual Treeview 所做的那样。也许您可以编写某种 TVirtualList 来调用适当的函数来在请求元素时收集所需的数据。

    【讨论】:

    • 从性能的角度来看,这不是一个好主意:TList 从一块内存中读取,在处理列表时调用“回调”方法来弥补数据不可能更快。这个想法的第二个问题是数据通常不是“组成”的,我怀疑它是否可以“即时”生成。类似 VirtualTree 的 GUI 假设存在一些保存数据的底层数据结构,并且它们根据需要调用方法来获取数据。他们这样做是为了避免内存中的数据重复(而且速度更快,因为它们不重复数据)。
    猜你喜欢
    • 1970-01-01
    • 2012-03-17
    • 2011-03-11
    • 1970-01-01
    • 2010-10-11
    • 1970-01-01
    • 2013-03-24
    • 2010-12-17
    • 1970-01-01
    相关资源
    最近更新 更多