紧密循环中的虚拟函数的成本
Cost of a virtual function in a tight loop
我的游戏对象有一个虚拟函数Update((。有很多游戏对象(目前有7000多个(,循环调用所有对象的更新(以及其他内容(。我的同事建议我们应该完全取消虚拟功能。正如你所能想象的,这将需要大量的重构。
我已经看到了这个答案,但在我的情况下,评测意味着我必须更改很多代码。所以,在我考虑开始之前,我想我会在这里征求一下关于重构在这种情况下是否值得的意见。
请注意,我已经分析了循环的其他部分,并试图优化耗时最长的部分。我怀疑在这种情况下,虚拟函数调用是我不应该担心的事情,但在我配置文件之前我不能确定,在我更改代码之前我不能配置文件(这是很多(。还要注意,有些更新函数非常小,而另一些更新函数则更大更复杂。
编辑:有多个答案可以提供很好的见解,所以任何将来偶然发现这个问题的人,都要看看所有的答案,而不仅仅是选定的答案。
虚拟函数调用只会增加一个间接寻址和一个难以预测的跳转。这意味着,通常情况下,每个虚拟函数要进行一次管道刷新或大约20个周期。其中7000个大约是140000个周期,与您的平均更新功能相比应该可以忽略不计。如果不是,比如说你的大多数更新函数都是空的,那么你可以考虑将可更新的对象放在一个单独的列表中。
删除虚拟功能只会导致你们中的一个人用一个完全相同但自行实现的系统来替换它。这正是虚拟功能有意义的地方。
根据参考文献,140000个周期约为50微秒。这是假设一个P4有一个巨大的管道,并且总是有一个完整的管道冲洗(你通常不会得到(。
虽然它与您使用的代码不同,也可能不是同一个编译器,但这里有一些来自相当旧的基准测试(Joe Orost的bench++(的参考数据:
Test Name: F000005 Class Name: Style
CPU Time: 7.70 nanoseconds plus or minus 0.385
Wall/CPU: 1.00 ratio. Iteration Count: 1677721600
Test Description:
Time to test a global using a 10-way if/else if statement
compare this test with F000006
Test Name: F000006 Class Name: Style
CPU Time: 2.00 nanoseconds plus or minus 0.0999
Wall/CPU: 1.00 ratio. Iteration Count: 1677721600
Test Description:
Time to test a global using a 10-way switch statement
compare this test with F000005
Test Name: F000007 Class Name: Style
CPU Time: 3.41 nanoseconds plus or minus 0.171
Wall/CPU: 1.00 ratio. Iteration Count: 1677721600
Test Description:
Time to test a global using a 10-way sparse switch statement
compare this test with F000005 and F000006
Test Name: F000008 Class Name: Style
CPU Time: 2.20 nanoseconds plus or minus 0.110
Wall/CPU: 1.00 ratio. Iteration Count: 1677721600
Test Description:
Time to test a global using a 10-way virtual function class
compare this test with F000006
这个特殊的结果来自于使用64位版本的VC++9.0(VS 2008(进行编译,但它与我从其他最近的编译器中看到的相当相似。底线是,虚拟函数比大多数明显的替代方案都快,非常接近于与唯一一个击败它的函数相同的速度(事实上,两者相等在测量的误差范围内(。然而,这取决于所涉及的值是否密集——正如你在F00007中看到的,如果值是稀疏的,switch语句产生的代码比虚拟函数调用慢。
一句话:虚拟函数调用可能找错地方了。重构后的代码可能很容易变得更慢,即使在最好的情况下,它也可能不会获得足够的关注。
如果您不能进行评测,请查看汇编代码,了解查找的实际成本。这可能是一个几乎不需要任何成本的简单间接跳转。
如果您需要重构,这里有一个建议:创建许多"UpdateXxx"类,这些类知道如何调用新的非虚拟update()
方法。将它们收集到一个数组中,然后对它们调用update()
。
但我的猜测是,你不会节省太多,尤其是只有7K对象的情况下。
关于评测的注意事项:如果您不能使用评测器(这让我想知道为什么不使用(,请为对update()
的调用计时,并记录耗时超过100ms的调用。时间安排并不昂贵,它可以让你快速找出哪些电话最贵。
您可以在这里找到另一个具有虚拟、内联和直接调用的测试[在此输入链接描述][1]虚拟功能和性能-C++
- C++无法定义虚拟函数 OUTER 类和头文件
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- 尝试将unique_ptrs推送到向量时使用纯虚拟函数错误
- 有没有比在库中添加一个并非由所有派生类实现的新虚拟函数更好的设计实践
- 重载 -> shared_ptr 个实例中的箭头运算符<interface>,接口中没有纯虚拟析构函数
- 当覆盖存在时调用基本虚拟"binded to object"函数
- 如何在C++中伪造虚拟可变参数函数模板?
- 类型擦除的std::function与虚拟函数调用的开销
- 重写虚拟函数和继承
- 是否可以使用函数指针调用虚拟析构函数?
- 在没有动态内存的世界中,我是否需要虚拟析构函数?
- 虚拟继承基构造函数消除
- "虚拟""覆盖"析构函数
- 类中的虚拟布尔函数参数不起作用
- 用纯虚拟函数兜圈子
- 将C++子类成员函数(虚拟实现)传递给 C 类型函数指针
- 尝试在 QLabel 上绘画失败(无法在没有对象的情况下调用成员函数"虚拟无效 QLabel::p aintEvent(QPaintEvent*)")
- 声明析构函数虚拟就足够了吗?
- 视觉 C++当我们在基类中使函数成为纯虚拟时,那么在子类中再次使相同的函数虚拟的必要性是什么
- 重载函数(虚拟/非虚拟)