对象容器的性能与指针容器的性能
Performance of container of objects vs performance of container of pointers
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 个尊重。大多数处理器在加载数据缓存时不遵循指针。这可能会影响性能,因为处理器在取消引用指向对象的指针时可能必须重新加载数据缓存。
智能指针向量
性能比原始指针稍微高一点。但是,当矢量被破坏时,这些项目将被自动删除。在 vector 被销毁之前,原始指针必须被删除;或创建内存泄漏。
总结
最安全的版本是在向量中有副本,但性能会受到影响,具体取决于对象的大小和重新分配保留内存区域的频率。由于双重取消引用,指针向量会影响性能,但在复制时不会产生额外的性能影响,因为指针的大小是一致的。与原始指针向量相比,智能指针向量可能会产生额外的性能损失。
可以通过分析代码找到真相。 在等待 I/O 操作(例如网络)时,一种数据结构相对于另一种数据结构的性能节省可能会消失或文件 I/O。
可能需要执行大量数据结构操作才能显着节省成本。例如,如果性能最差的数据结构和最好的数据结构之间的差异是 10 纳秒,这意味着您将需要至少执行 1E+6 次才能使节省显着。如果一秒很重要,则期望访问数据结构更多次 (1E+9)。
我建议选择一种数据结构并继续。您开发代码的时间比程序运行的时间更有价值。安全性和稳健性也更为重要。与安全可靠的版本相比,不安全的程序会花费更多的时间来解决问题。
对于普通旧数据 (POD) 类型,至少在 sizeof(POD) > sizeof(POD*) 之前,该类型的向量总是比指向该类型的指针向量更有效。
对于 POD 类型,几乎总是如此,至少直到 sizeof(POD) > 2 * sizeof(POD*)要指向的位置。
在 sizeof(POD) 超过您的体系结构、编译器和使用的某个阈值之前,这种分析将一直有效,您需要通过基准测试通过实验发现这些阈值。上面只是为 POD 类型设置了该大小的下限。
很难说出关于所有非 POD 类型的任何定论,因为它们的操作(例如 - 默认构造函数、复制构造函数、赋值等)可能与 POD 一样便宜或任意更昂贵。
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 个尊重。大多数处理器在加载数据缓存时不遵循指针。这可能会影响性能,因为处理器在取消引用指向对象的指针时可能必须重新加载数据缓存。
智能指针向量
性能比原始指针稍微高一点。但是,当矢量被破坏时,这些项目将被自动删除。在 vector 被销毁之前,原始指针必须被删除;或创建内存泄漏。
总结
最安全的版本是在向量中有副本,但性能会受到影响,具体取决于对象的大小和重新分配保留内存区域的频率。由于双重取消引用,指针向量会影响性能,但在复制时不会产生额外的性能影响,因为指针的大小是一致的。与原始指针向量相比,智能指针向量可能会产生额外的性能损失。
可以通过分析代码找到真相。 在等待 I/O 操作(例如网络)时,一种数据结构相对于另一种数据结构的性能节省可能会消失或文件 I/O。
可能需要执行大量数据结构操作才能显着节省成本。例如,如果性能最差的数据结构和最好的数据结构之间的差异是 10 纳秒,这意味着您将需要至少执行 1E+6 次才能使节省显着。如果一秒很重要,则期望访问数据结构更多次 (1E+9)。
我建议选择一种数据结构并继续。您开发代码的时间比程序运行的时间更有价值。安全性和稳健性也更为重要。与安全可靠的版本相比,不安全的程序会花费更多的时间来解决问题。
对于普通旧数据 (POD) 类型,至少在 sizeof(POD) > sizeof(POD*) 之前,该类型的向量总是比指向该类型的指针向量更有效。
对于 POD 类型,几乎总是如此,至少直到 sizeof(POD) > 2 * sizeof(POD*)要指向的位置。
在 sizeof(POD) 超过您的体系结构、编译器和使用的某个阈值之前,这种分析将一直有效,您需要通过基准测试通过实验发现这些阈值。上面只是为 POD 类型设置了该大小的下限。
很难说出关于所有非 POD 类型的任何定论,因为它们的操作(例如 - 默认构造函数、复制构造函数、赋值等)可能与 POD 一样便宜或任意更昂贵。