【问题标题】:C++ How does a vector of pointers affect performance?C++ 指针向量如何影响性能?
【发布时间】:2017-09-16 08:51:38
【问题描述】:

我想知道指向对象的 std::vector 与使用直接包含对象的 std::vector 相比如何影响程序的性能。具体来说,我指的是程序的速度。

我被教导要使用 std::vector 而不是其他 STL,例如 std::list 以提高速度,因为它的所有数据都连续存储在内存中,而不是碎片化。这意味着迭代元素很快,但是我的想法是,如果我的向量包含指向对象的指针,那么对象仍然可以存储在内存中的任何位置,并且只有指针连续存储。我想知道在迭代向量和访问对象时这将如何影响程序的性能。

我当前的项目设计使用指针向量,以便我可以利用虚函数,但是我不确定这是否值得我的向量变得非常大时可能遇到的速度损失。感谢您的帮助!

【问题讨论】:

  • 如果对象是多态的,你必须使用指针。如果将其设为对象向量,则在将它们存储到向量中时会对它们进行切片。如果你需要维护对象的身份,你会失去对象向量,因为它会复制。
  • 指针是连续存储的。指针引用的数据是Crom-knows-where。确定这是否是性能问题的唯一方法是分析这两个选项。但在您开始执行此操作之前,当前版本是否符合所需的性能规范?有什么需要改进的地方吗?
  • 我怀疑这是否是您减速的原因。
  • 通过分析器运行你的程序,看看哪一段代码占用了你的时间,然后寻找并消除那段代码中的低效率。如果事实证明这就是您存储数据的方式,那就这样吧,但在做出决定时没有什么比硬数字更重要的了。破解代码是因为你有一种感觉……我猜对绝地有用。 Meesa no Jedi,好吗?看看使用 valgrind 或 Visual Studio 中内置的任何东西。如果你有所有可用的,gprof 总比没有好。

标签: c++ performance pointers vector stl


【解决方案1】:

如果您需要多态性,正如人们所说,您应该存储指向基址的指针。如果稍后您认为此代码很热并且需要优化其 cpu 缓存使用情况,您可以通过使对象完全适合缓存通道和/或使用自定义分配器来确保取消引用数据的代码局部性来做到这一点。

切片是当您通过值 Base 存储对象并复制构造或为其分配 Derived 时,Derived 将被 Sliced,复制构造函数或分配器仅采用 Base 并将忽略 Derived 中的任何数据,没有足够的空间在 Base 中分配以获取 Derived 的完整大小。即,如果 Base 为 8 字节,而 Derived 为 16,则即使您提供了显式采用 Derived 的复制构造函数/分配器,目标值中也只有足够的空间容纳 Base 的 8 字节。

我应该说,如果您大量使用不会被优化器忽略的虚拟化,那么考虑数据缓存一致性真的不值得。指令缓存未命中比数据缓存未命中更具破坏性,并且虚拟化会导致指令缓存未命中,因为它必须在将函数加载到指令缓存之前查找 vtable 指针,因此无法抢先加载它们。

CPU 倾向于将尽可能多的数据预加载到缓存中,如果您加载一个地址,则整个缓存通道(约 64 字节)将被加载到缓存通道中,并且通常它还会在之前和这就是为什么人们如此热衷于数据本地化的原因。

因此,在您的指针向量场景中,当您加载第一个指针时,您将在缓存中一次获得大量指针,如果您的实际粒子通过指针加载将触发缓存未命中并加载该对象周围的数据是 16 个字节并且彼此本地,除此之外你不会失去太多。如果它们遍布整个堆并且非常庞大,那么每次迭代都会使缓存变得非常混乱,并且在处理粒子时相对没问题。

传统上,粒子系统往往非常热并且喜欢将数据紧密打包,通常会看到 16 字节的普通旧数据粒子系统,您可以通过非常可预测的分支对其进行线性迭代。这意味着您通常可以依赖每个缓存通道 4 个粒子,并让预取器保持领先于您的代码。

我还应该说 cpu 缓存依赖于 cpu,我专注于 intel x86。例如,Arm 往往落后于英特尔,而且管道不那么复杂,预取器的能力较差,因此缓存未命中的破坏性较小。

【讨论】:

  • 感谢您的回答,非常有见地,我不了解缓存未命中的具体情况,您的示例帮助我理解了它们。非常感谢您解释切片!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-09-13
  • 1970-01-01
  • 2020-04-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多