vftable性能损失与switch语句

时间:2010-12-17 03:45:57

标签: c++ performance virtual

C ++问题。我有一个系统,我将拥有数百个给定超类的迷你子类。他们都会有一个“foo”方法做某事。或者......我将有一个带有一个名为“type”的整数的类,并使用一个巨大的switch语句来决定当我foo时该做什么。

性能是一个重要的考虑因素。非常重要。

问题是,使用switch语句与让C ++通过vftable执行此操作相比,性能优势/惩罚是什么?如果我将它作为switch语句,我可以将常见的foo放在switch语句的顶部,而不常见的那些放在底部,希望能够快速进行比较。尝试使用vftable获得这样的效果必然会依赖于编译器,即使我可以弄清楚如何做到这一点......

另一方面,如果没有这些丑陋的switch语句,我的代码将更容易处理。

5 个答案:

答案 0 :(得分:13)

  

如果我把它作为一个switch语句,我可以把常见的foo放在switch语句的顶部,而不太常见的那些放在底部,希望能够快速进行比较。

正如您的问题所暗示的那样,switch语句通常会编译为jump table而不是if-else条件块。在实践中,虚拟表和switch跳转表应具有相似的性能,但如果您真的担心的话,请进行测试。

答案 1 :(得分:12)

在虚拟机设计领域已经对这一主题进行了一些研究。通常,switch语句会更快,许多虚拟机使用切换语义而不是虚拟查找。从理论上讲,人们会认为虚拟表 - 一个恒定时间算法 - 会更快,但我们必须检查硬件如何看待虚拟表。

编译器内联时,switch语句更容易。这是一个很大的考虑因素,调用虚函数的实际行为是最小的,但是,推送和弹出整个堆栈帧是必要的,因为编译器不知道在运行时将调用哪个函数。

尽管现代架构在预测虚拟呼叫方面越来越好,但在switch语句中分支预测和硬件预取应该更容易。

许多使用虚拟分派的代码都需要使用基于堆的分配方案。动态内存分配是很多C ++应用程序的瓶颈。

答案 2 :(得分:3)

编译器确定如何处理switch语句,但它们使用了一些基本技术。

  1. if-else binary-sort:比较是作为一系列if-else完成的,但是以二进制排序方式完成,因此性能与N个项目的地图中的查找相当
  2. 跳转表:如果项目足够接近,将生成一个地址表。然后查找是在恒定的时间
  3. 如果case语句位于switch语句中,则两种情况都没有区别。

    与直接呼叫相比,虚拟功能具有开销。它涉及额外的偏移和指针查找。除了最极端的性能考虑外,这个成本可以忽略不计。与交换机进行比较时,开销不在虚拟查找中,而是函数调用自身。因此,在每种情况下简单地调用函数的switch语句将与虚函数基本相同。

    因此,与虚函数调用相比,switch语句(带跳转表)的“调度语义”基本上是无关紧要的。如果所有“foo”方法都相对较小且可以内联,则switch语句将开始执行得更好。 switch的另一个优点是你可以在交换机之前放置公共代码,并获得更好的寄存器/堆栈优化。

    然而,存在显着的维护开销。在这一点上,这应该是您最关心的问题。为什么?因为代码中的性能瓶颈不太可能是切换登录,甚至是函数调用,而是其他东西。在你解决其他问题之前,解决这些低级性能问题毫无意义。因此,请坚持使用目前提供更易维护的代码。

答案 3 :(得分:2)

对于其他答案,我还要再添加两个。

1)编译器在虚函数调用接口上执行经典优化(包括注册)比在单个函数中的switch语句中执行经过案例标记的语句更困难且更不常见。

2)调度中的任何性能差异都非常依赖于处理器的分支预测硬件。即使虚拟函数调用目标地址(和返回)也可以正确预测,并且在现代无序处理器的流水线中具有可忽略的性能开销。

如果此操作的性能确实很重要,那么您必须在真实系统的上下文中尝试两种方式并进行测量。

快乐的黑客攻击!

答案 4 :(得分:0)

几乎在所有情况下,Vtable应该更快,但如果性能如此重要,那么正确的问题就是多少。

Vtable调用是三重间接(三次内存访问以获取目标CALL地址)。如果有很多呼叫,缓存未命中应该不是问题。因此,它大约是2-3个开关标签比较(虽然后者提供的CPU缓存丢失的可能性更小,但管道使用的可能性更小)。

您当然不应该依赖我在此处所说的任何内容,并使用目标架构上的真实性能测量来测试所有内容。