cha*_*m15 42 c c++ optimization gcc compiler-optimization
下面的 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;
}
}
Run Code Online (Sandbox Code Playgroud)
下面生成一个用于返回的数据表:
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;
}
}
Run Code Online (Sandbox Code Playgroud)
为什么gcc无法将顶部的优化为底部的呢?
拆解参考: https: //godbolt.org/z/UP_igi
编辑:有趣的是,MSVC 为 switch case 生成一个跳转表而不是数据表。令人惊讶的是,clang 将它们优化到相同的结果。
th3*_*3lf 31
生成的代码switch-case通常使用跳转表。在这种情况下,通过查找表直接返回似乎是一种优化,利用了这里每个案例都涉及返回的事实。尽管该标准不保证这种效果,但如果编译器生成一系列比较而不是传统 switch-case 的跳转表,我会感到惊讶。
现在来看if-else,情况恰恰相反。虽然switch-case无论分支数量如何,都会以恒定时间执行,if-else针对较少数量的分支进行了优化。在这里,您希望编译器基本上按照您编写的顺序生成一系列比较。
因此,如果我使用它是if-else因为我希望大多数调用square()都是针对0or1而很少针对其他值,那么将其“优化”为表查找实际上可能会导致我的代码运行速度比我预期的慢,从而违背了我if使用的switch. 因此,尽管存在争议,但我认为 GCC 正在做正确的事情,而 clang 在优化方面过于激进。
有人在评论中分享了一个链接,其中 clang 执行此优化并生成基于查找表的代码if-else。当我们使用 clang 将案例数量减少到只有两个(和默认值)时,会发生一些值得注意的事情。它再次为 if 和 switch 生成相同的代码,但这一次,
两者都切换为比较和移动,而不是查找表方法。这意味着即使是喜欢 switch 的 clang 也知道,当案例数量较少时,“if”模式更为最佳!
总之,比较序列if-else和跳转表switch-case是编译器倾向于遵循的标准模式,也是开发人员在编写代码时倾向于期望的标准模式。然而,对于某些特殊情况,一些编译器可能会选择打破这种模式,他们认为这种模式可以提供更好的优化。其他编译器可能只是选择坚持该模式,即使显然不是最理想的,相信开发人员知道他想要什么。两者都是有效的方法,各有优点和缺点。
一个可能的理由是,如果 的值num更可能较低(例如始终为 0),则第一个生成的代码可能会更快。生成的 switch 代码对于所有值都花费相同的时间。
如果num == 0,对于“如果”,你有 xor、test、je(带有跳转)、ret。延迟:1+1+跳跃。然而,xor 和 test 是独立的,因此实际执行速度会比 1 + 1 个周期更快。
如果num < 7,对于“switch”,您有 mov、cmp、ja(无跳转)、mov、ret。延迟:2 + 1 + 无跳跃 + 2。
不导致跳转的跳转指令比导致跳转的跳转指令更快。但是,该表没有定义跳转的延迟,因此我不清楚哪一个更好。有可能最后一个总是更好,而 GCC 根本无法优化它。