考虑到新的CPU带有移动和新内存控制器的新指令,如果在C ++中我有一个Derived
对象的向量,其中Derived
由虚拟成员函数组成,这是一个好的还是一个当地的坏事?
如果我有一个指向基类Base*
的指针向量,我在哪里存储对Base
1-2-3级别的派生对象的引用?
基本上动态类型适用于这两种情况,但哪一种更适合缓存和内存访问?
我对这两者有偏好,但我希望看到关于这个主题的完整答案。
在过去的2 - 3年里,硬件行业还有一些新的东西被认为是地面制动?
答案 0 :(得分:1)
在向量中存储Derived
而不是Base *
更好,因为它消除了一个额外的间接级别,并且所有对象在连续内存中“一起”布局,这反过来使生活更轻松对于硬件预取器,有助于分页,TLB未命中等。但是,如果这样做,请确保不引入切片问题。
对于这种情况下的虚拟调度,除了“this”指针所需的调整之外几乎无关紧要。例如,如果Derived
覆盖了您正在调用的虚函数并且您已经指向Devied *
,则不需要«this»调整,否则应调整为其中一个基数class`s«this»值(这也取决于继承层次结构中类的大小)。
只要向量中的所有类具有相同的重载,CPU就能够预测正在发生的事情。但是,如果您混合使用不同的实现,那么CPU将不知道将为每个下一个对象调用哪个函数,这可能会导致性能问题。
并且不要忘记在进行更改之前和之后始终进行配置。
答案 1 :(得分:1)
现代CPU知道如何优化与数据相关的跳转指令,以及它可以用于数据相关的“分支”指令 - 处理器将“学习”“上次我经历过这里,我走了这条路”,并且如果它有足够的信心(经历了几次相同的结果),就会继续这样做。
当然,如果实例是不同类的完全随机选择,每个类都有自己的虚函数,那么这无济于事。
缓存局部性当然是一个稍微不同的问题,它实际上取决于您是存储对象实例还是向量中的实例的指针/引用。
当然,一个重要因素是“替代方案是什么?” - 如果您正确使用虚函数,则意味着在代码路径中至少有一个条件检查(因为该决策是在更早的阶段进行的)。如果你通过其他方法解决这个问题,那么这个条件将是(假设概率对应于相同的概率)决策的分支概率 - 这对于具有相同概率的virtual
函数的性能至少同样差(可能性更糟,因为我们现在有一个if (x) foo(); else bar();
类型的场景,所以我们首先必须评估x
然后选择路径。obj->vfunc()
将是不可预测的,因为获取vtable给出了一个不可预测的结果 - 但至少vtable
本身是缓存的。