c++ - 为什么在 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 将它们优化为相同的结果。
解决方案
生成的代码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
是编译器倾向于遵循的标准模式,开发人员在编写代码时也倾向于期望。但是,对于某些特殊情况,一些编译器可能会选择在他们认为提供更好优化的地方打破这种模式。其他编译器可能只是选择坚持这种模式,即使显然不是最佳的,相信开发人员知道他想要什么。两者都是有效的方法,各有优缺点。
推荐阅读
- eclipse-rcp - Eclipse RCP 插件代码覆盖与 Jacoco 和 Tycho 万无一失
- python - Python调用字符串的变量名一个int
- javascript - React js:会话过期后如何自动注销?
- c# - c# Thread.Equals vs Thread.CurrentThread.ManagedThreadId 比较
- javascript - Highcharts 渲染周期(无数标签格式化程序调用)
- python - scikit-learn train_test_split 是否保留关系?
- java - 基于选择结果的基本锁定
- .htaccess - htaccess 文件中 https 的重定向规则不起作用
- c - 将提交推送到 GitHub
- python - Python Pandas:删除A列第一个空单元格下方的所有数据行