在 switch case 中的默认情况

4
下面是我需要优化的代码,计划将其转换为 switch 结构。但我可以在 case 中进行比较。所以我打算将比较 (len > 3) 作为 default 案例。
如果我将比较部分 (len > 3) 作为默认情况,并将 default 添加为开头的情况,这样会更快吗?
或者我该如何将下面的代码转换为 switch 语句?
if ( len > 3 ) {
    // Which will happen more often;
}
else if ( len == 3 ) {
    // Next case which may occur often;

} else if ( len == 2 ) {
    // The next priority case;

} else {
    // And this case occurs rarely;

}

你不能将从4到最大值的范围移动到switch中。它需要编译时的case标签。 - chris
@chris 谢谢你的回复。但我记得我在某个地方学过,即使你有两个以上的else if,最好也要转换成switch? - 2vision2
当然,如果这是一个可能的移动,它看起来会更好。但这个不行。 - chris
@chris 如果这个程序的性能真的很关键(我有点怀疑),将不等式转换为直接(可切换)比较是完全可能的。 - JasonD
@JasonD,我想你可以将> 3else都放到default情况中。但是,试图将> 3包含在情况中并不值得做。 - chris
显示剩余3条评论
6个回答

6
可能不是。if...else 和 switch...case 都是高级结构。导致速度变慢的是分支预测,你的预测越好,你的代码就会运行得更快。应该将出现频率最高的情况放在第一个 if 语句中,第二个出现频率次高的情况放在 else if 中,以此类推,就像你写的那样。对于 switch 语句,结果取决于内部编译器实现,即使按照你自己的顺序排序,它也可以重新排序这些情况。default 实际上应该为不经常出现的情况保留,因为在回退到默认情况之前必须检查其余条件。
总之,在性能方面,只要按照正确的顺序设置条件,使用 if...else 是最优的。关于 switch...case,它是编译器特定的,取决于应用的优化。
还要注意,switch...case 比 if...else 更加有限,因为它仅支持简单的值比较。

无论如何,谢谢... 我只是想知道 switch 内部是如何实现或工作的。我在某个地方读过,但找不到我读过的来源。抱歉让你再次烦恼。 - 2vision2
1
据我所知,一些编译器实际上会使用二分查找来“实现”具有足够多 case 的 switch 语句,因此它比纯线性的 if ... else 组合更好。 - Angew is no longer proud of SO
@Angew 这可能非常特定于使用情况。 - SomeWittyUsername
1
@2vision2 加油!可能有人可以提供一些见解。你也可以尝试在http://cs.stackexchange.com上发问。 - SomeWittyUsername
@icepack 在这里发布了一个帖子,并找到了一些有趣的链接...看一下吧。谢谢。 - 2vision2
显示剩余15条评论

3

虽然您已经接受了可能是最好的答案,但我想提供一种替代方案。

请注意,标准警告适用-除非对代码进行了分析,否则优化不是优化。

但是,如果您遇到与分支相关的性能问题,则可以减少或消除它们。您的代码具有一个或多个不等式比较并不是障碍-您可以将您的情况减少到一组直接相等,并在必要时使用它来索引表格,而不需要进行任何分支。

void doSomething(int len)
{
    static const char* str[] =
    {   "%2d > 3\n",
        "%2d < 2\n",
        "%2d = 2\n",
        "%2d = 3\n"
    };

    int m1 = (len-2)>>31;
    int m2 = (len-4)>>31;

    int r = (len & m2 & ~m1) + !!m1;

    printf(str[r],len); 
}

请注意,这些代码作出了几个假设,实际情况可能不成立。但是,我们假设这需要优化,因此需要进行优化...
此外,请注意,如果有关于输入参数的实际范围和类型以及所需执行的实际操作的更多了解,可能会有更好的优化方法。

有趣,我现在明白你的意思了。我没有想太多关于 switch 语法之外的事情。这对一个程序员来说是不合适的! - chris
嗨,Jason。非常感谢你的回答。它很有趣。这鼓励像我这样的学习者经常访问SO。 - 2vision2
有趣的是,你可以避免任何分支,但代价是什么呢?考虑到额外操作的数量和一个条件远比其他条件频繁,一个好的编译器将安排这个条件不进行分支(假设正确预测的分支确实很昂贵),因此上述所有内容实际上只是混淆。 - James Kanze
正如我在开头所说的那样,如果你没有进行过性能分析,那么这并不是优化。尽管如此,我肯定曾经使用过一些编译器,对于这些编译器而言,这将是一个胜利。但是,在没有进行性能分析的情况下做出任何假设都是愚蠢的。@JamesKanze - JasonD

2
您不能将比较操作放入 switch 语句中,因为它只使用单一检查来进行选择,例如:
switch (len) {

    case 1:
        // Do case 1 stuff here
    break;

    case 2:
        // Do case 2 stuff here
    break;

    case 3:
        // Do case 3 stuff here
    break;
}

使用break语句可以防止case语句互相干扰。在这里阅读更多内容。

在当前状态下,您的代码已经尽可能地被“优化”了。


1
唯一的方法是使用编译器进行基准测试。如果性能是一个问题,你应该使用选项来提供编译器分析输出,并让它决定;通常它会找到最佳解决方案。(请注意,即使在特定架构上,如英特尔,最佳机器指令解决方案也可能因处理器而异。)
在你的情况下,开关可能看起来像这样:
switch ( len ) {
case 2:
    //  ...
    break;

case 3:
    //  ...
    break;

default:
    if ( len > 3 ) {
        // ...
    } else {
        // ...
    }
}

只有两个有效的情况,编译器没有太多可以处理的内容。一个典型的实现(没有极端优化)会进行边界检查,然后进行两个明确情况的表查找。任何好的编译器都会注意到你的default case中的比较与它已经完成的边界检查之一相对应,并且不会重复执行。但是,只有两种情况,跳转表可能不会与两个比较相比产生显着差异,特别是在最常见的情况下,你将超出范围。

除非你有实际的分析器信息表明这是代码的瓶颈,否则我不会担心它。一旦你获得了这些信息,你可以分析不同的变体来确定哪个更快,但我怀疑如果你使用最大的优化并将分析信息反馈给编译器,就不会有任何区别。


1

如果您担心速度问题,那么事实上,除非您有数百个if...elseswitch...case语句,否则它们不会对应用程序速度产生真正的影响。您失去速度的地方是在迭代或循环中。具体回答您的问题,您不能将if...else语句转换为以default开头的switch...case语句;但是话虽如此,如果您确实转换为switch...case,那么它们运行的速度相同(差异太微小,无法被传统基准测试工具检测出来)。


3
如果在循环中使用if else或switch case语句,将会产生非常实际的影响。 - SomeWittyUsername
我认为这取决于循环的大小。但在这种情况下,只需要检查四个条件;差异会有许多小数位,不是需要开发人员特别关注的领域。 - JustKash

0

你可以在case语句中使用范围:

switch (len) {
  case 3 ... INT_MAX:
    // ...
    break;
  case 2:
    // ...
    break;
  default:
    // ...
    break;
 }

但那是GCC提供的一个扩展...


它说这不是标准的做法:现在还是这样吗?https://dev59.com/Q2025IYBdhLWcg3wkW8F - Caribou
这是一个不错的结构 :) 我以前从未见过,很好奇。 - Caribou
@Caribou 嗯,正如你的链接所示,使用 -pedantic 选项编译确实会警告“非标准化”(gcc 4.7.2);不过这不在标准中。 - piwi
我其实也同意 - 我想知道编译器是否可以使用这种方式进行任何优化?还是它变成了与“if..else”代码相同的东西。也许可以在喝咖啡休息时研究一下 :) - Caribou

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