首页 > 解决方案 > 为什么在 c/c++ 中,开关的优化方式与链式 if else 不同?

问题描述

Square 的以下实现会产生一系列 cmp/je 语句,就像我期望的链式 if 语句一样:

int square(int num) {
    if (num == 0){
        return 0;
    } else if (num == 1){
        return 1;
    } else if (num == 2){
        return 4;
    } else if (num == 3){
        return 9;
    } else if (num == 4){
        return 16;
    } else if (num == 5){
        return 25;
    } else if (num == 6){
        return 36;
    } else if (num == 7){
        return 49;
    } else {
        return num * num;
    }
}

以下生成返回的数据表:

int square_2(int num) {
    switch (num){
        case 0: return 0;
        case 1: return 1;
        case 2: return 4;
        case 3: return 9;
        case 4: return 16;
        case 5: return 25;
        case 6: return 36;
        case 7: return 49;
        default: return num * num;
    }
}

为什么 gcc 无法将 top 优化为 bottom ?

拆解参考: https ://godbolt.org/z/UP_igi

编辑:有趣的是,MSVC 为 switch case 生成了一个跳转表而不是一个数据表。令人惊讶的是,clang 将它们优化为相同的结果。

标签: c++cgccoptimizationcompiler-optimization

解决方案


生成的代码switch-case通常使用跳转表。在这种情况下,通过查找表直接返回似乎是一种优化,利用了这里每个案例都涉及返回的事实。尽管该标准不保证这种效果,但如果编译器要生成一系列比较而不是传统 switch-case 的跳转表,我会感到惊讶。

现在来到if-else,情况正好相反。虽然switch-case在恒定时间内执行,但无论分支数量如何,if-else都针对较少数量的分支进行了优化。在这里,您会期望编译器基本上按照您编写它们的顺序生成一系列比较。

因此,如果我之所以使用if-else是因为我希望大多数调用square()是针对0或很少针对其他值,那么将其“优化”为表查找实际上可能会导致我的代码运行速度比我预期的要慢,从而违背1了我使用 an 的目的if一个switch。因此,尽管值得商榷,但我认为 GCC 正在做正确的事情,而 clang 在优化方面过于激进。

有人在评论中分享了一个链接,clang 在该链接中进行了优化并生成了基于查找表的代码if-else。当我们使用 clang 将案例数量减少到两个(和一个默认值)时,会发生一些值得注意的事情。它再次为 if 和 switch 生成相同的代码,但这一次, 切换到比较和移动而不是查找表方法,两者都是。这意味着即使是喜欢切换的 clang 也知道当案例数量较少时,“if”模式更为优化!

总之,一系列比较if-else和跳转表switch-case是编译器倾向于遵循的标准模式,开发人员在编写代码时也倾向于期望。但是,对于某些特殊情况,一些编译器可能会选择在他们认为提供更好优化的地方打破这种模式。其他编译器可能只是选择坚持这种模式,即使显然不是最佳的,相信开发人员知道他想要什么。两者都是有效的方法,各有优缺点。


推荐阅读