虚拟成员函数对于现代 CPU 中的局部性是好是坏?

Posted

技术标签:

【中文标题】虚拟成员函数对于现代 CPU 中的局部性是好是坏?【英文标题】:virtual member functions are good or bad for locality in modern CPUs? 【发布时间】:2013-08-07 16:28:53 【问题描述】:

考虑到带有用于移动和新内存控制器的新指令的新 CPU,如果在 C++ 中我有一个 Derived 对象向量,其中 Derived 由虚拟成员函数组成,这对地点?

如果我有一个指向基类Base* 的指针向量,我在其中存储对从Base 高1-2-3 级的派生对象的引用?

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

我对这两个有偏好,但我希望看到关于这个主题的完整答案。

在过去的 2 到 3 年里,硬件行业有什么新的突破可以考虑?

【问题讨论】:

我确信更深的流水线已经改善了这种情况,但是虚函数总是需要依赖于数据的跳转,这将停止流水线直到获取函数指针。 您是否尝试过基准测试? @DietrichEpp 不,因为我对这种情况下的理论感兴趣,所以我确信即使没有基准测试我也可以从这 2 中选择一个。 【参考方案1】:

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

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

只要向量中的所有类都具有相同的重载,CPU 就能够预测正在发生的事情。但是,如果您混合了不同的实现,那么 CPU 将不知道将为每个下一个对象调用什么函数,这可能会导致性能问题。

并且不要忘记在进行更改之前和之后始终进行概要分析。

【讨论】:

对于“切片问题”,您的意思是“确保仅将 Derived 类型的对象分配给 Derived 的向量”对吗?没有什么是隐含的? @user2485710:正确,即不要使用本身派生自Derived 的对象调用push_back()。但是..有一件事让我想起了。如果你知道所有对象都是Derived 类型(如果你有它们的向量,你肯定知道),那么你根本不需要使用多态性和虚函数...... :) mmm ...我提出这种情况与使用引用相反,但你可能是对的,它没有多大意义,结果我可能根本不在乎关于这一点,因为只有参考才能说明这种情况。【参考方案2】:

现代 CPU 知道如何优化依赖于数据的跳转指令,以及优化依赖于数据的“分支”指令 - 处理器将“学习”“上次我经过这里,我走了这条路”,并且如果它有足够的信心(经历了几次相同的结果),它就会一直这样下去。

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

缓存局部性当然是一个稍微不同的问题,它实际上取决于您是存储对象实例还是向量中实例的指针/引用。

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

【讨论】:

顺便说一句,我得出的结论是,在 x86_64 英特尔 CPU 上,您最好最多执行 3-4 个分支,而不是一个与数据相关的跳转。我自己做了一些基准测试,结果还发现 GCC 最多生成 3 个比较以避免跳转(即在实现稀疏 switch 语句时,从 4.8 开始,您也可以从命令行调整和调整这个数字)。跨度> “如果你正确使用虚函数”是什么意思;您也基本上是在说,如果所有可能的路径都是一小部分,那么差异应该不会很明显? @VladLazarenko 有趣的是,我在 Intel 上使用 x86_64 的经验并不多,因为我在家里使用 AMD 处理器,这是我做大部分“有趣”工作的地方。 @user2485710:选择的数量并不像“从一个对象切换到另一个对象的频率”那么重要。因此,如果您创建一些图形函数,其中虚拟选择是使用 OpenGL 或 DirectX 进行绘制,那么该选择可能在整个代码运行时中是不变的 - 因此虚拟函数将受到很少的惩罚。另一方面,如果您有一些从一个呼叫到下一个呼叫的变化,那么处理器将无法预测呼叫。而“正确使用虚函数”,我的意思是使用它们,因为在代码中可以做出选择......

以上是关于虚拟成员函数对于现代 CPU 中的局部性是好是坏?的主要内容,如果未能解决你的问题,请参考以下文章

iFrame 是好是坏? [复制]

比特币为什么会被国家打压?打压是好是坏?

比特币为什么会被国家打压?打压是好是坏?

比特币为什么会被国家打压?打压是好是坏?

使用字符串而不是符号:是好是坏?

具有相同名称属性的输入字段的多个表单?是好是坏?