【发布时间】:2020-02-08 10:35:08
【问题描述】:
我最近在做一个个人项目时偶然发现了一个奇怪的问题。
在一个非常紧凑的循环中,我有一个值介于 0 和 15 之间的整数。对于值 0、1、8 和 9,我需要得到 -1,对于值 4、5、12 和 13,我需要得到 1。
我求助于 Godbolt 检查了几个选项,并惊讶于编译器似乎无法像 if 链一样优化 switch 语句。
链接在这里:https://godbolt.org/z/WYVBFl
代码是:
const int lookup[16] = {-1, -1, 0, 0, 1, 1, 0, 0, -1, -1, 0, 0, 1, 1, 0, 0};
int a(int num) {
return lookup[num & 0xF];
}
int b(int num) {
num &= 0xF;
if (num == 0 || num == 1 || num == 8 || num == 9)
return -1;
if (num == 4 || num == 5 || num == 12 || num == 13)
return 1;
return 0;
}
int c(int num) {
num &= 0xF;
switch (num) {
case 0: case 1: case 8: case 9:
return -1;
case 4: case 5: case 12: case 13:
return 1;
default:
return 0;
}
}
我原以为 b 和 c 会产生相同的结果,并且我希望我可以阅读 bit-hacks 以自己提出一个有效的实现,因为我的解决方案(switch 语句 - 以另一种形式)是相当慢。
奇怪的是,b 被编译为 bit-hack,而 c 要么几乎没有优化,要么根据目标硬件减少为 a 的不同情况。
谁能解释为什么会出现这种差异?优化此查询的“正确”方法是什么?
编辑:
澄清
我希望切换解决方案是最快的,或者类似的“干净”解决方案。但是,当在我的机器上进行优化编译时,if 解决方案明显更快。
我编写了一个快速程序来演示,TIO 的结果与我在本地找到的结果相同:Try it online!
使用static inline,查找表会加快一点:Try it online!
【问题讨论】:
-
我怀疑答案是“编译器并不总是做出明智的选择”。我刚刚用 GCC 8.3.0 用
-O3将你的代码编译成一个对象,它把c编译成可能比a或b更糟糕的东西(c有两个条件跳转加上一些位操作,与b的只有一个条件跳转和更简单的位操作相比),但仍然比天真的逐项测试要好。我不确定您在这里真正要的是什么;一个简单的事实是,优化编译器可以将其中的 any 转换为其他的 any ,如果它愿意的话,并且对于它会做什么没有硬性规定或不会的。 -
我的问题是我需要它快速,但 if 解决方案不是过度可维护的。有没有办法让编译器充分优化更清洁的解决方案?谁能解释为什么在这种情况下它不能这样做?
-
我会首先将至少函数定义为静态的,或者——甚至更好地——内联它们。
-
@wildplasser 确实加快了速度,但
if仍然胜过switch(奇怪的是查找变得更快)[TIO 关注] -
@LambdaBeta 无法告诉编译器以特定方式进行优化。您会注意到 clang 和 msvc 为它们生成完全不同的代码。如果您不在乎,只想在 gcc 上最有效,那么选择它。编译器优化是基于启发式的,并且在所有情况下都不会产生最佳解决方案;他们试图在一般情况下做到最好,而不是在所有情况下都做到最佳。
标签: c gcc optimization bit-manipulation disassembly