如果可以估计的话,TList、TObjectList 和普通数组之间的性能差异有多明显?

发布于 2024-10-24 06:48:49 字数 1020 浏览 5 评论 0原文

*总结:

请查看 Delphi 专家的专业评论。特别是对于我来说,我会尝试按照 David 的建议使用旧的 TList/TObjectList,并按照 A.Bouchez 的建议使用硬转换和 TObjectList.List 属性。以后重构的时候我会尝试TDynArray。

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

假设我有一个 TAtom 类,如以下代码中所定义。运行时大约有数百数千 TAtom 实例,目前存储在动态数组中。在运行时,我需要对所有现有 TAtom 实例的 TAtom.X/Y/Z 进行简单的浮点数学运算,每秒超过 30 次。

现在,我需要添加在运行时添加插入删除 TAtom 实例的功能。看来我的选择是(1)请求一个大数组; (2) 坚持动态数组并手动SetLength; (3)切换到常规TList; (4)切换到常规TObjectList。

除非有必要,否则我想避免 (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;

*Summarization:

Please check the knowledgeable comments from the Delphi experts. Specifically for me, I would try to use old TList/TObjectList as David suggested, and use hard-cast and TObjectList.List property as A.Bouchez suggested. I will try TDynArray when refactoring in future.

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

Say that I have a TAtom class as defined in the following code. There are about hundreds up to thousands of TAtom instances at run time, stored in a dynamic array for now. At run time, I need to do simple float math on TAtom.X/Y/Z of all the existing TAtom instances more than 30 times per second.

Now, I need to add the ability of adding, inserting, deleting of TAtom instances at run time. It seems that my choices are (1) request a big array; (2) stick to dynamic array and manually SetLength; (3) switch to regular TList; (4) switch to regular TObjectList.

I want to avoid (1) unless it is necessary, because I then have to change quite a lot function signatures. (2) looks not good either, because TList/TObjectList seems born for this task. However, because type-casting is needed using the regular TList/TObjectList, could some one comment on the possible performance hit? I mean, it would be best if the performance burden could be estimated before I rewrites the code. If the performance will drop noticeably, is there other technics that I could use?

Furthermore, I am wondering if there is performance difference between using TList and TObjectList?

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

  TAAtom = array of TAtom;

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

荒芜了季节 2024-10-31 06:48:50

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

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

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

对于插入和删除,如果您有大量项目,则此类记录的数组将比 TList 慢,因为将有更多数据需要删除/插入 (TList/TObjectList< /code> 都只维护一个指针列表)。为了更快地插入/删除,您最好使用链表。

由于内部通知,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 支持泛型,您应该更好地朝这个方向发展。

May I add another choice to your list?

If you don't use any inheritance feature for the data in TAtom, you could use a record instead of a class. Each class instance will require to be allocated in memory, filled with zero and initialized individually. Getmem/Freemem always cost, and memory fragmentation will increase.

A pre-allocated dynamic array of record will be faster than individual class instances for adding. And the data will fit better for CPU L1/L2 cache.

For inserting and deleting, an array of such records will be slower than TList if you have a huge number of items, because there'll be more data to delete/insert (TList/TObjectList both maintain just a list of pointers). For even faster insertion/deletion, you should better use a linked list.

There is some overhead in the TList/TObjectList mechanism because of internal notification. mechanism And the GetItem() property could be a bit slower (because of range checking) than using directly a dynamic array.

But with our TDynArray wrapper, you could stick to a dynamic array, and still have good performance, pre-allocation features, and TList-like methods. And even more methods available, like SaveToStream, Slice, Reverse, sorting with external indexes and such...

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

Works with Delphi 6 up to XE.

With newer version of Delphi supporting generics, you should better go into this direction.

素罗衫 2024-10-31 06:48:50

如果您使用 Generics.Collections.TObjectList 并且不需要进行转换。

对于您所描述的用法来说,性能应该很好。插入比添加到末尾要求更高,因为您需要将插入点之后的项目在列表中向上移动。

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

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

这一切都有点推测性,你确实需要测量——否则我们只能猜测。

If you use Generics.Collections.TObjectList<TAtom> and there's no need for casting.

Performance should be fine for the usage that you describe. Inserting is more demanding than adding to the end because you need to shift the items after the insertion point up the list.

So long as you avoid SetLength(A, Length(A)+1) and opt for a more sensible allocation strategy dynamic arrays are equivalent to all of the TList like classes.

On occasions I have had problems with performance and memory fragmentation when trying to maintain large lists as contiguous blocks of memory. Then I have resorted to a sub-allocation scheme. But since your lists contain object references which are essentially pointers, you already have implicit sub-allocation.

It's all somewhat speculative and you really need to measure – otherwise we can only guess.

青丝拂面 2024-10-31 06:48:50

但是,因为类型转换是
需要使用常规的
TList/TObjectList,有人可以吗
对可能的表现发表评论
命中?

如果您使用表单输入cast,

List[I] as TAtom

则会增加少量开销,这在您的情况下确实会增加。 如果您进行硬类型转换

TAtom(List[I])

但是,据我所知,

,实际上不会发生任何开销,因为类型转换是在没有检查的情况下执行的,而且我也相信它是在编译时完成的。至于你问题的另一方面,我认为它们已经全部得到了适当的涵盖......

However, because type-casting is
needed using the regular
TList/TObjectList, could some one
comment on the possible performance
hit?

If you type cast using the form

List[I] as TAtom

as small overhead will be added, which can really add up in your situation. However, if you hard typecast

TAtom(List[I])

as far as I know, no overhead actually occurs as the typecast is performed without checks and I also believe it is done at compile time.

As for the other aspect of your question, I think they have been all properly covered already...

守不住的情 2024-10-31 06:48:50

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

在添加、插入和删除时,TList 和 TObjectList 可能比动态数组更快,因为动态数组需要不断地重新分配。 TList 和 TObjectList 的实现不会这样做。

Make a test project and measure the time of adding, inserting and deleting thousands of TAtom instances using the four methods. Then decide which one to use.

TList and TObjectList are probably faster than a dynamic array when adding, inserting and deleting, because the dynamic array constantly has to be reallocated. The implementation of TList and TObjectList doesn't do that.

烟燃烟灭 2024-10-31 06:48:50

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

As TObjectList is a direct descendant of TList the performances will be very close.

复古式 2024-10-31 06:48:50

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

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


如果我们谈论“较旧的”TListTObjectList,那么我们只需将 TList 与等效的动态数组进行比较,因为 < code>TObjectList 是 TList 的后代,因此它继承了它的所有性能特征。 TList 使用通过ReallocMem 分配的内存块。动态数组在内部执行相同的操作,因此不应该有显着差异!

结论

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

First question: Are we talking about Classes.TList and Contnrs.TObjectList or are we talking about Generics.Collections.TList respectively Generics.Collections.TObjectList ?

If we're talking about generics, both TList and TObjectList are implemented using dynamic arrays. If there's any performance difference between directly using a dynamic array or using the nicer interface of the generic container, it's going to be negligible.


If we're talking about the "older" TList and TObjectList, then we need to compare only TList with the dynamic array equivalent, since TObjectList is an descendant of TList so it inherits all it's performance characteristics. TList uses a block of memory allocated using ReallocMem. The dynamic array does the same thing internally, so there shouldn't be a significant difference!

Conclusion

If there's any performance difference between the two, it's probably because naive use of dynamic array uses the dreaded SetLength(A, Length(A)+1), while the better implementation in all Delphi-provided containers pre-allocate memory in larger chunks. With proper code there shouldn't be any significant difference between those alternative!

Hello爱情风 2024-10-31 06:48:50

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

一项标准可能是读取/更新与序列的比率。如果序列在创建后很少更新,那么使用动态数组应该可以感觉到更好的速度,因为使用 TList 等访问元素需要一个方法调用加上边界检查,如果使用的话还需要进行类型检查as 运算符。

最后,在 TAtom 上完成的算术成本应该主导运行时间,从而使 dynarray 或 TListXXX 的选择变得无关紧要。

TList etc. do exactly what code working on chunks of memory or dynarrays would have to do, but their implementation is already optimized for the common case, and includes considerations about how the memory manager behaves.

One criteria could be the ratio of reads/updates to the sequence. If the sequence is updated infrequently after created, then there should be perceivable better speed with dynarrays, because access to elements with TList and the likes requires one method call plus bounds checking, plus a type check if you use the as operator.

In the end, the cost of arithmetic done on TAtom should dominate the run time, making the choice of dynarray or TListXXX irrelevant.

一笔一画续写前缘 2024-10-31 06:48:50

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

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

  • 如果您不需要通过
  • 树索引访问原子,那么您可能需要考虑:如果您需要使用原子的空间分区,那么您也可以使用该分区来保存你的原子而不是数组
  • 允许数组/列表中的未定义/零元素,并维护一堆未定义/零元素,如果你需要一个排序列表,还需要一个索引(可能是性能最高的解决方案,但也可能是最复杂的)就效率而言是正确的)

A dynamic arrays of records will have the lowest impact when accessing the items, if your atoms are objects, then all the lists are going to be somewhat equivalent in terms of access speed.

However, if you perform many of them, your key issue will be the inserts and deletes, for which all lists and arrays will perform poorly, but that is what profiling will tell you.
If that's the case, you might then want to consider:

  • a linked list if you don't need to access atoms by index
  • a tree, should you have use for a space partition of your atoms, you might as well use that partition to hold your atoms rather than the array
  • allowing undefined/nil elements in your array/list, and maintaining a stack of undefined/nil elements, and an index if you need a sorted list (potentially the highest performance solution, but also likely the most complex to get right in terms of efficiency)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文