【问题标题】:Performance of container of objects vs performance of container of pointers对象容器的性能与指针容器的性能
【发布时间】:2015-03-09 18:38:15
【问题描述】:
class C { ... };
std::vector<C> vc;
std::vector<C*> pvc;
std::vector<std::unique_ptr<C>> upvc;

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

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

【问题讨论】:

  • 对什么有效?这实际上取决于您如何处理这些向量。如果您对性能感兴趣,则必须对其进行衡量。
  • 可能还取决于您对容器的操作。我总是按值存储,而不是指针,直到我可以衡量这是不好的。到目前为止,我从来没有发生过。
  • 你说的是内存开销吗?您是在谈论向量操作的运行时成本(例如 - 排序)吗?你想最大限度地提高什么样的效率?
  • 制作一个模板,在大对象的情况下分配指针或在对象小的情况下不分配指针应该解决这个问题吗?
  • 我可以为这个尺寸设定一个明确的下限。对于 POD 类型,您应该至少在 sizeof(POD) > sizeof(POD*) 之前更喜欢该类型的向量。不太明确的是,您应该至少在 sizeof(POD) > 2 * sizeof(POD*) 之前更喜欢这种类型的向量,因为它具有优越的内存局部性和较低的内存使用率(特别是如果您正在动态分配要指向的对象) .

标签: c++ pointers c++11 optimization containers


【解决方案1】:

是的,这是可能的 - 对其进行基准测试。由于这些天 CPU 缓存的工作方式,事情已经不简单了。

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

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

【讨论】:

    【解决方案2】:

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

    对象向量

    对象向量首先具有初始性能影响。当一个对象被添加到向量中时,它会创建一个副本。 当需要扩展保留的内存时,向量也会进行复制。 较大的对象将需要更多的时间来复制,以及复杂或复合的对象。

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

    原始指针向量

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

    将指针复制到向量中不依赖于对象大小。根据对象大小,这可能会节省性能。

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

    智能指针向量

    在性能上比原始指针要贵一点。但是,当向量被破坏时,这些项目将被自动删除。在销毁向量之前必须删除原始指针;或创建内存泄漏。

    总结

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

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

    数据结构的操作可能需要执行大量时间才能显着节省。例如,如果性能最差的数据结构和最好的数据结构之间的差异是 10 纳秒,这意味着您需要执行至少 1E+6 次才能显着节省。如果一秒钟很重要,则期望访问数据结构的次数更多(1E+9)。

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

    【讨论】:

    • 我说的是“可能”,这意味着您必须进行剖析才能找到答案。取决于unique 指针的实现。除了unique_ptr,还有其他智能指针。您还可以查看编译器生成的汇编语言列表。
    • 不,unique_ptr 不会比使用手动内存管理的原始指针慢。
    【解决方案3】:

    对于普通旧数据 (POD) 类型,至少在 sizeof(POD) > sizeof(POD*) 之前,该类型的向量总是比指向该类型的指针向量更有效。

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

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

    很难对所有非 POD 类型做出任何明确的决定,因为它们的操作(例如 - 默认构造函数、复制构造函数、赋值等)可能与 POD 一样便宜,或者任意更贵。

    【讨论】:

      猜你喜欢
      • 2022-01-23
      • 1970-01-01
      • 1970-01-01
      • 2019-07-09
      • 2014-03-04
      • 2014-08-09
      • 2021-07-03
      • 2014-05-07
      • 1970-01-01
      相关资源
      最近更新 更多