对象容器的性能与指针容器的性能

Performance of container of objects vs performance of container of pointers

本文关键字:性能 指针 对象      更新时间:2023-10-16
class C { ... };
std::vector<C> vc;
std::vector<C*> pvc;
std::vector<std::unique_ptr<C>> upvc;

根据 C 的大小,按值存储的方法或按指针存储的方法将更有效。

是否可以大致知道此大小是多少(在 32 位和 64 位平台上)?

是的,这是可能的 - 基准测试它。由于如今CPU缓存的工作方式,事情不再简单。

看看Bjarne Stroustrup关于链表的讲座:https://www.youtube.com/watch?v=YQs6IC-vgmo

以下是 Scott Meyers 关于 CPU 缓存的精彩演讲: https://www.youtube.com/watch?v=WDIkqP4JbkE

在得出任何结论之前,让我们先看看每个示例的细节。

对象的向量

对象的向量首先受到初始性能影响。 将对象添加到矢量时,它会创建副本。 当矢量需要扩展保留内存时,它还将制作副本。 较大的对象以及复杂或复合对象将需要更多时间来复制。

访问对象非常有效 - 只有一个取消引用。 如果您的向量可以放入处理器的数据缓存中,这将非常有效。

原始指针的矢量

这可能会影响初始化性能。 如果对象位于动态内存中,则必须首先初始化(分配)内存。

将指针复制到矢量中不依赖于对象大小。 这可能会节省性能,具体取决于对象大小。

访问对象会影响性能。 在到达对象之前有 2 个尊重。 大多数处理器在加载数据缓存时不遵循指针。 这可能会降低性能,因为处理器在取消引用指向对象的指针时可能必须重新加载数据缓存。

智能指针的矢量

性能比原始指针贵一点。 但是,当矢量被破坏时,这些项目将自动删除。 必须先删除原始指针,然后才能销毁矢量;或创建内存泄漏。

总结

最安全的版本是在矢量中包含副本,但性能会受到影响,具体取决于对象的大小和重新分配保留内存区域的频率。 指针向量由于双重取消引用而受到性能影响,但在复制时不会产生额外的性能影响,因为指针的大小一致。 与原始指针向量相比,智能指针向量可能会受到额外的性能影响。

真正的真相可以通过分析代码来找到。 在等待 I/O 操作(如网络或文件 I/O)时,一种数据结构相对于另一种数据结构的性能节省可能会消失。

使用数据结构的操作可能需要执行大量次才能节省大量成本。 例如,如果性能最差的数据结构与性能最佳的数据结构之间的差异为 10 纳秒,这意味着您需要至少执行 1E+6 次才能显著节省成本。 如果一秒很重要,则预计访问数据结构的次数更多次 (1E+9)。

我建议选择一种数据结构并继续前进。 您开发代码的时间比程序运行的时间更有价值。 安全性和稳健性也更为重要。 不安全的程序将比安全且强大的版本花费您更多的时间来解决问题。

对于普通旧数据 (POD) 类型,至少在 sizeof(POD)

> sizeof(POD*) 之前,该类型的向量总是比指向该类型的指针向量更有效。

几乎总是如此,至少在 sizeof(POD)> 2 * sizeof(POD*) 之前,POD 类型也是如此,因为与动态分配要指向的对象相比,它具有卓越的内存局部性和更低的总内存使用量。

这种分析将一直有效,直到 sizeof(POD) 超过您的架构、编译器和用法的某个阈值,您需要通过基准测试通过实验发现这些阈值。 以上仅对 POD 类型的该大小设置了下限。

很难说所有非 POD 类型都是确定的,因为它们的操作(例如 - 默认构造函数、复制构造函数、赋值等)可能与 POD 一样便宜或任意昂贵。