8

*总结:

请查看 Delphi 专家的知识渊博的评论。特别是对我来说,我会尝试按照 David 的建议使用旧的 TList/TObjectList,并按照 A.Bouchez 的建议使用 hard-cast 和 TObjectList.List 属性。将来重构时我会尝试 TDynArray。

==================================================== ====================

假设我有一个TAtom在以下代码中定义的类。目前,在运行时大约有hundreds多达TAtom 实例。在运行时,我需要对所有现有的 TAtom 实例进行简单的浮点数学运算,超过每秒次数。 thousandsstored in a dynamic arrayTAtom.X/Y/Z30

现在,我需要在运行时添加 TAtom 实例的adding能力。看来我的选择是(1)请求一个大数组;(2)坚持动态数组,手动设置长度;(3) 切换到常规TList;(4) 切换到常规TObjectList。 insertingdeleting

除非有必要,否则我想避免(1),因为我必须更改很多函数签名。(2) 看起来也不好,因为 TList/TObjectList 似乎是为这项任务而生的。但是,因为需要使用常规 TList/TObjectList 进行类型转换,所以有人可以评论可能的性能损失吗?我的意思是,最好在我重写代码之前估计性能负担。如果性能会明显下降,我可以使用其他技术吗?

此外,我想知道使用 TList 和 TObjectList 之间是否存在性能差异?

  TAtom = class
  public
    ElementZ: Integer;
    X, Y, Z: Extended;  
    other variables: other types;
  end;

  TAAtom = array of TAtom;
4

8 回答 8

8

我可以在您的列表中添加另一个选择吗?

如果您不对 中的数据使用任何继承功能,则TAtom可以使用 arecord而不是 a class。每个类实例都需要在内存中分配,用零填充并单独初始化。Getmem/Freemem总是成本,并且内存碎片会增加。

预先分配的动态array of record将比添加单个类实例更快。并且数据将更适合 CPU L1/L2 缓存。

对于插入和删除,这样的记录数组将比TList拥有大量项目时要慢,因为要删除/插入的数据更多(TList/TObjectList两者都只维护一个指针列表)。为了更快地插入/删除,您应该更好地使用链表。

TList/TObjectList由于内部通知,该机制存在一些开销。机制并且该GetItem()属性可能比直接使用动态数组要慢一些(因为范围检查)。

但是使用我们的TDynArray 包装器,您可以坚持使用动态数组,并且仍然具有良好的性能、预分配功能和TList类似的方法。还有更多可用的方法,比如SaveToStream, Slice, Reverse使用外部索引排序等等......

type
  TAtom = record // could be 'packed record' to save memory (but loose perf)
    ElementZ: Integer;
    X, Y, Z: Extended;  
    other variables: other types;
    // TDynArray also handle complex (e.g. string) types here
  end;
  TAtoms = array of TAtom;

var Atom: TAtom;
    AtomArray: TAtoms;
    AtomCount: integer;
    Atoms: TDynArray;
begin
  Atoms.Init(TypeInfo(TAtoms),AtomArray,@AtomCount);
  Atoms.Capacity := 10000; // pre-allocate array = same as SetLength(AtomArray,10000)
  for i := 1 to 10000 do begin
    A.ElementZ := Random(1000);
    A.X := Random;
    A.Y := Ramdom;
    A.Z := Random;
    // set other fields
    Atoms.Add(A); // fast adding of A properties
  end;
  // you have TList-like methods for your dynamic array
  Atoms.Delete(500); // delete 500th item
  A.ElementZ := 5000;
  Atoms.Insert(500,A); // insert A values at 500th index
  assert(Atoms.Count=10000);
  assert(AtomCount=10000); // same as Atoms.Count
  Atoms.Compare := SortDynArrayInteger;
  Atoms.Sort; // will sort by 1st Integer value = ElementZ
  for i := 1 to Atoms.Count-1 do // or AtomCount-1
    // you have still direct access to AtomArray[]
    // -> this is even the fastest access to the data
    assert(AtomArray[i].ElementZ >=AtomArray[i-1].ElementZ )
  Atoms.SaveToStream(aStream); // will also save any string content
  Atoms.Reverse; // reverse all items order
  Atoms.Clear;
  // faster adding will be done with direct access to the dynamic array
  Atom.Count := 10000; // allocate memory for 10000 items
  for i := 0 to 10000-1 do
  with AtomArray[i] do
  begin
    ElementZ := Random(2000);
    X := Random;
    Y := Random;
    Z := Random;
  end;
  Atoms.Sort; // TDynArray knows about the data just created
end; // no need to have any try...finally ..Free block

适用于 Delphi 6 至 XE。

随着新版本的 Delphi 支持泛型,您最好朝这个方向发展。

于 2011-03-18T13:01:46.483 回答
7

如果您使用Generics.Collections.TObjectList<TAtom>并且不需要铸造。

对于您描述的用法,性能应该没问题。插入比添加到末尾要求更高,因为您需要将插入点之后的项目向上移动。

只要您避免SetLength(A, Length(A)+1)并选择更明智的分配策略,动态数组就等同于所有TList类似的类。

在尝试将大型列表维护为连续的内存块时,有时我会遇到性能和内存碎片问题。然后我采用了子分配方案。但是由于您的列表包含本质上是指针的对象引用,因此您已经有了隐式子分配。

这有点投机,你真的需要衡量——否则我们只能猜测。

于 2011-03-18T12:49:35.090 回答
3

但是,因为需要使用常规 TList/TObjectList 进行类型转换,所以有人可以评论可能的性能损失吗?

如果您使用表单输入 cast

List[I] as TAtom

因为会增加少量开销,这在您的情况下确实会增加。但是,如果你硬类型转换

TAtom(List[I])

据我所知,实际上没有开销,因为类型转换是在没有检查的情况下执行的,我也相信它是在编译时完成的。

至于你问题的另一方面,我认为它们都已经被适当地涵盖了......

于 2011-03-18T14:22:34.457 回答
1

做一个测试项目,用四种方法测量添加、插入和删除数千个 TAtom 实例的时间。然后决定使用哪一个。

TList 和 TObjectList 在添加、插入和删除时可能比动态数组快,因为动态数组必须不断地重新分配。TList 和 TObjectList 的实现并没有做到这一点。

于 2011-03-18T12:42:46.163 回答
1

由于 TObjectList 是 TList 的直接后代,因此性能将非常接近。

于 2011-03-18T12:42:51.040 回答
1

第一个问题:我们是在谈论Classes.TList还是Contnrs.TObjectList我们Generics.Collections.TList分别在谈论Generics.Collections.TObjectList

如果我们谈论的是泛型,那么 TList 和 TObjectList 都是使用动态数组实现的。如果直接使用动态数组或使用通用容器的更好接口之间有任何性能差异,那将可以忽略不计。


如果我们谈论的是“较旧的” TListand TObjectList,那么我们只需要TList与等效的动态数组进行比较,因为TObjectList它是它的后代,TList所以它继承了它的所有性能特征。TList使用使用分配的内存块ReallocMem。动态数组在内部做同样的事情,所以应该没有显着差异!

结论

如果两者之间有任何性能差异,可能是因为对动态数组的天真使用使用了 dreaded SetLength(A, Length(A)+1),而在所有 Delphi 提供的容器中更好的实现预先分配了更大的内存块。使用正确的代码,这些替代方案之间不应该有任何显着差异!

于 2011-03-18T12:48:16.353 回答
1

TList等。完全执行在内存块或 dynarray 上工作的代码必须执行的操作,但它们的实现已经针对常见情况进行了优化,并且包括有关内存管理器行为方式的考虑。

一个标准可以是读取/更新与序列的比率。如果序列在创建后不经常更新,那么使用 dynarrays 应该会有更好的速度,因为访问元素TList等需要一个方法调用加上边界检查,如果使用as运算符,还需要进行类型检查。

最后,计算的成本TAtom应该支配运行时间,这使得选择 dynarray 或TListXXX无关紧要。

于 2011-03-18T13:02:01.023 回答
1

在访问项目时,动态记录数组的影响最小,如果您的原子是对象,那么所有列表在访问速度方面都将是相当的。

但是,如果您执行其中的许多操作,您的关键问题将是插入和删除,所有列表和数组的性能都会很差,但这就是分析会告诉您的。如果是这种情况,您可能需要考虑:

  • 如果您不需要按索引访问原子,则为链表
  • 一棵树,如果您有用于原子的空间分区,您不妨使用该分区来保存您的原子而不是数组
  • 允许数组/列表中的未定义/零元素,并维护一堆未定义/零元素,如果需要排序列表,则保留索引(可能是性能最高的解决方案,但在效率方面也可能是最复杂的解决方案)
于 2011-03-18T13:29:23.310 回答