紧密循环中的虚拟函数的成本

Cost of a virtual function in a tight loop

本文关键字:函数 虚拟 循环      更新时间:2023-10-16

我的游戏对象有一个虚拟函数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++