C++函数指针与Switch语句比较

19
  • 函数指针和switch哪个更快?

switch语句将具有约30个case,由从0到30枚举的无符号整数组成。

我可以这样做:

class myType
{
    FunctionEnum func;
    string argv[123];
    int someOtherValue;
};
// In another file:
myType current;
// Iterate through a vector containing lots of myTypes
 // ... for ( i=0; i < myVecSize; i ++ )
    switch ( current.func )
    {
           case 1:
            //...
            break;
           // ........
           case 30:
             // blah
            break;
    }

每次都使用func通过开关。使用开关的好处在于,比使用30个函数代码更加有组织。

或者我也可以这样做(不太确定):

class myType
{
    myReturnType (*func)(int all, int of, int my, int args );
    string argv[123];
    int someOtherValue;
};

我会有30个不同的函数,一开始将其中一个函数的指针赋值给myType。

  • 哪个可能更快:switch语句还是函数指针?

每秒调用次数:约1000万次。我无法进行测试 - 那需要我重写整个程序。目前正在使用switch语句。

我正在构建一个解释器,希望比Python和Ruby更快 - 每个时钟周期都很重要!


你是指 myReturnType (*func)(); 吗? - kennytm
我正在构建一个解释器。经典的解决方案是使用计算跳转:https://eli.thegreenplace.net/2012/07/12/computed-goto-for-efficient-dispatch-tables - Jean-Michaël Celerier
4个回答

16

通常使用跳转表来实现 switch 语句。我认为汇编指令可以减少到一条,这将使其非常快。

要确定的唯一方法是尝试两种方式。如果您无法修改现有代码,为什么不创建一个测试应用程序并在那里尝试呢?


1
除非你的编译器做得很糟糕,否则 switch 至少和函数指针一样快。使用函数指针只是选择了一种实现 switch 的策略,这可能是最好的方法,也是编译器会选择的方法,但会存在参数传递的开销。如果任何一个 case 语句块有任何公共代码,编译器可能会发现更好的方法。我建议 OP 只使用 switch,不要浪费时间尝试两种方式。可以安全地假设 switch 可能会编译成良好的代码。如果它在性能分析中是一个热点,请检查汇编代码。 - Peter Cordes

5
我认为在大多数情况下差异是可以忽略的 - 你的情况可能是个例外。为什么不制作一个简单的原型应用程序来明确测量每种解决方案的性能呢?我猜这不会超过一小时的工作时间...
然而,也要考虑代码的清晰度和可维护性。在我看来,函数指针解决方案在这方面显然更胜一筹。
更新:还要注意,即使一种解决方案比另一种解决方案快两倍,它现在仍然不一定能够证明重写代码的必要性。首先,您应该对您的应用程序进行分析,以确定执行时间的实际花费在这些开关上。如果它占总时间的50%,那么优化它是有理由的。如果只有几个百分点,那么优化它将是徒劳的。

唯一的switch情况只有10-20行。函数并不能更易读。写这个可能需要一个多小时,可能要一天 - 我没有那么多时间。我的问题实际上与我的应用程序架构无关 - 它只是指函数指针与最低级别下的switch性能。 - Rawr
很遗憾,你没有告诉我任何新的东西:我应该编写两个版本的应用程序来测试它,这是不可能的,而且仅凭一个原型也无法充分反映最终版本。 - Rawr
1
我可能完全漏掉了什么,但在我看来,测试程序需要大约3个类:一个带有switch语句的单个类方法的实现,另一个使用函数指针的相同方法,以及一个测试类(甚至是一个“main”方法),该类执行每个方法1000万次并测量执行时间。 - Péter Török
我认为函数指针在清晰度和可维护性方面并不明显。在我看来,除非需要灵活性,否则函数指针是次优的选择。如果有一个(小)固定的有效延续集合,则函数指针解决方案无法诚实地说明或强制执行。另一方面,具有n个情况的开关,其中每个情况直接调用另一个函数,可以清楚地说明可能发生的情况,并且这不是可维护性问题。 - Jo So

3

Péter Török的想法是尝试两种方法并计时,这个想法是正确的。您可能不喜欢它,但不幸的是这是现实情况。 “过早优化”的呼声是有原因的。

我总是支持从一开始就使用性能最佳实践,只要不牺牲清晰度。但在这种情况下,对于您提到的任何一个选项,都没有明显的获胜者。在大多数情况下,这种小改变几乎没有可测量的影响。会有一些主要瓶颈完全控制整个系统的速度。在现代计算机上,这里和那里的几个指令基本上是看不见的,因为系统被内存缓存未命中或流水线停顿所阻塞,而这些很难预测。例如,在您的情况下,单个额外的缓存未命中可能会决定哪种方法更慢。

现实世界的解决方案是使用分析器评估整个系统的性能。这将向您展示代码中的“热点”位置。通常的下一步是通过更好的算法或缓存来减少对热代码的许多调用,以降低其性能需求。只有当非常小的代码部分点亮分析器时,才值得进行这样的微小微调优化。在那时,您必须尝试不同的方法并测试其对速度的影响。如果没有测量更改的效果,即使对于专家来说,您也很可能使情况变得更糟。

话虽如此,我会猜测,在您的情况下,如果函数调用具有非常少的参数,特别是每个案例的主体都很大,则其可能会稍微快一些。如果switch语句不使用跳转表,则可能会更慢。但它可能会因编译器和机器架构而异,因此除非您以后有确凿的证据表明它是系统的瓶颈,否则不要花太多时间。编程与写新代码一样重要的是重构代码。


良好的评论,Alan,但“热点”的概念本身就让我感到困扰。它似乎来自于一个旧观念,即如果花费太多时间,则唯一的超支方式是PC寄存器在需要更改的某些位置上花费了太多时间。基于这个概念的任何分析器都非常容易被欺骗,而调用图也不会好到哪里去。https://dev59.com/xHI-5IYBdhLWcg3wlpeW#1779343 - Mike Dunlavey

0
编写解释器非常有趣,不是吗?我猜函数指针可能更快,但在优化时,猜测并不能带你走太远。
如果你真的想要挤出那个野兽的每一个周期,就需要进行多次遍历,猜测不会告诉你要修复什么。这里有一个我所说的例子。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接