在现代CPU中,虚拟成员函数对局部性是好是坏

virtual member functions are good or bad for locality in modern CPUs?

本文关键字:局部性 函数 虚拟成员 CPU      更新时间:2023-10-16

考虑到具有新的移动指令和新的内存控制器的新CPU,如果在C++中,我有一个Derived对象的向量,其中Derived由虚拟成员函数组成,这对局部性来说是好事还是坏事?

如果我有一个指向基类Base*的指针向量,在那里我存储对从Base向上1-2-3级别的派生对象的引用,该怎么办?

基本上,动态类型适用于这两种情况,但哪一种更适合缓存和内存访问?

我比较喜欢这两个,但我希望看到这个问题的完整答案。

在过去的2-3年里,硬件行业有什么新的东西可以考虑作为地面制动?

Derived而不是Base *存储在向量中更好,因为它消除了一个额外的间接级别,并且您可以将所有对象"一起"布置在连续内存中,这反过来又使硬件预取器的生活更轻松,有助于分页、TLB未命中等。但是,如果您这样做,请确保不会引入切片问题。

至于这种情况下的虚拟调度,除了«this»指针所需的调整外,几乎无关紧要。例如,如果Derived覆盖您正在调用的虚拟函数,并且您已经有指向Devied *的指针,则不需要进行«this»调整,否则应将其调整为基类的«this»值之一(这也取决于继承层次结构中类的大小)。

只要向量中的所有类都有相同的重载,CPU就能够预测发生了什么。然而,如果你有不同的实现,那么CPU就不知道下一个对象将调用什么函数,这可能会导致性能问题。

别忘了在你做出改变之前和之后都要进行个人简介。

现代CPU知道如何优化依赖数据的跳转指令,以及如何优化依赖于数据的"分支"指令-处理器会"学习"上次我在这里,我是这样做的",如果它有足够的信心(经历了几次相同的结果),它会继续这样做。

当然,如果实例是对不同类的完全随机选择,并且每个类都有自己的虚拟函数,那么这也无济于事。

缓存局部性当然略有不同,它实际上取决于您是在向量中存储对象实例还是指向实例的指针/引用。

当然,一个重要的因素是"替代方案是什么?"——如果你"正确"地使用虚拟函数,这意味着在代码路径中(至少)少了一个条件检查,因为决策是在更早的阶段做出的。该条件将是(假设概率对应相同)决策的分支概率,如果你用其他方法来解决它——这对性能的影响至少和具有相同概率的virtual函数一样糟糕(可能情况更糟,因为我们现在有一个if (x) foo(); else bar();类型的场景,所以我们必须首先评估x,然后选择路径。obj->vfunc()将是不可预测的,因为获取vtable会产生不可预测结果,但至少vtable本身是缓存的。