很抱歉不同意当前接受的答案。这是 2021 年。现代编译器及其优化器不应再区分 switch 和等效的 if-chain。如果他们仍然这样做,并且为任一变体创建了优化不佳的代码,那么请写信给编译器供应商(或在此处公开,这具有更高的受尊重变化),但不要让微优化影响您的编码风格。
所以,如果你使用:
switch (numError) { case ERROR_A: case ERROR_B: ... }
或:
if(numError == ERROR_A || numError == ERROR_B || ...) { ... }
或:
template<typename C, typename EL>
bool has(const C& cont, const EL& el) {
return std::find(cont.begin(), cont.end(), el) != cont.end();
}
constexpr std::array errList = { ERROR_A, ERROR_B, ... };
if(has(errList, rnd)) { ... }
在执行速度方面应该没有区别。但是根据您正在从事的项目,它们可能会在编码清晰度和代码可维护性方面产生重大影响。例如,如果您必须在代码的许多地方检查某个错误列表,则模板化的has() 可能更容易维护,因为 errList 只需要在一个地方更新。
谈到当前的编译器,我已经用clang++ -O3 -std=c++1z(版本10和11)和g++ -O3 -std=c++1z编译了下面引用的测试代码。两个 clang 版本都给出了类似的编译代码和执行时间。所以我从现在开始只谈论版本 11。最值得注意的是,functionA()(使用if)和functionB()(使用switch)产生与clang完全相同的汇编输出!并且functionC() 使用了跳台,尽管许多其他海报认为跳台是switch 的独有功能。然而,尽管许多人认为跳转表是最佳的,但这实际上是clang 上最慢的解决方案:functionC() 需要比functionA() 或functionB() 多20% 的执行时间。
手动优化版本functionH() 是迄今为止clang 上最快的版本。它甚至部分展开循环,在每个循环上进行两次迭代。
实际上,clang 计算了位域,它在functionH()、functionA() 和functionB() 中也明确提供。但是,它在functionA() 和functionB() 中使用了条件分支,这使得这些分支变得缓慢,因为分支预测经常失败,而它在functionH() 中使用了效率更高的adc(“带进位相加”)。虽然它未能在其他变体中应用这种明显的优化,但我不知道。
g++ 生成的代码看起来比clang 的代码复杂得多——但实际上functionA() 的运行速度要快一些,functionC() 的运行速度要快得多。在非手动优化的函数中,functionC() 是 g++ 上最快的,并且比 clang 上的任何函数都快。相反,functionH() 在使用g++ 而不是clang 编译时需要两倍的执行时间,主要是因为g++ 不进行循环展开。
以下是详细结果:
clang:
functionA: 109877 3627
functionB: 109877 3626
functionC: 109877 4192
functionH: 109877 524
g++:
functionA: 109877 3337
functionB: 109877 4668
functionC: 109877 2890
functionH: 109877 982
如果在整个代码中将常量32 更改为63,则性能会发生巨大变化:
clang:
functionA: 106943 1435
functionB: 106943 1436
functionC: 106943 4191
functionH: 106943 524
g++:
functionA: 106943 1265
functionB: 106943 4481
functionC: 106943 2804
functionH: 106943 1038
加速的原因是,如果最高测试值为 63,编译器会删除一些不必要的绑定检查,因为无论如何rnd 的值绑定到 63。请注意,删除绑定检查后,在 g++ 上使用简单的 if() 的非优化 functionA() 的执行速度几乎与手动优化的 functionH() 一样快,并且它还产生相当相似的汇编程序输出。
结论是什么?如果您大量手动优化和测试编译器,您将获得最快的解决方案。任何假设 switch 或 if 更好,都是无效的 - 它们在 clang 上是相同的。用于检查array 值的简单编码解决方案实际上是g++ 上最快的情况(如果省略手动优化和按事件匹配列表的最后一个值)。
未来的编译器版本会越来越好地优化你的代码,更接近你的手工优化。所以不要在这上面浪费你的时间,除非周期对你来说真的很重要。
这里是测试代码:
#include <iostream>
#include <chrono>
#include <limits>
#include <array>
#include <algorithm>
unsigned long long functionA() {
unsigned long long cnt = 0;
for(unsigned long long i = 0; i < 1000000; i++) {
unsigned char rnd = (((i * (i >> 3)) >> 8) ^ i) & 63;
if(rnd == 1 || rnd == 7 || rnd == 10 || rnd == 16 ||
rnd == 21 || rnd == 22 || rnd == 63)
{
cnt += 1;
}
}
return cnt;
}
unsigned long long functionB() {
unsigned long long cnt = 0;
for(unsigned long long i = 0; i < 1000000; i++) {
unsigned char rnd = (((i * (i >> 3)) >> 8) ^ i) & 63;
switch(rnd) {
case 1:
case 7:
case 10:
case 16:
case 21:
case 22:
case 63:
cnt++;
break;
}
}
return cnt;
}
template<typename C, typename EL>
bool has(const C& cont, const EL& el) {
return std::find(cont.begin(), cont.end(), el) != cont.end();
}
unsigned long long functionC() {
unsigned long long cnt = 0;
constexpr std::array errList { 1, 7, 10, 16, 21, 22, 63 };
for(unsigned long long i = 0; i < 1000000; i++) {
unsigned char rnd = (((i * (i >> 3)) >> 8) ^ i) & 63;
cnt += has(errList, rnd);
}
return cnt;
}
// Hand optimized version (manually created bitfield):
unsigned long long functionH() {
unsigned long long cnt = 0;
const unsigned long long bitfield =
(1ULL << 1) +
(1ULL << 7) +
(1ULL << 10) +
(1ULL << 16) +
(1ULL << 21) +
(1ULL << 22) +
(1ULL << 63);
for(unsigned long long i = 0; i < 1000000; i++) {
unsigned char rnd = (((i * (i >> 3)) >> 8) ^ i) & 63;
if(bitfield & (1ULL << rnd)) {
cnt += 1;
}
}
return cnt;
}
void timeit(unsigned long long (*function)(), const char* message)
{
unsigned long long mintime = std::numeric_limits<unsigned long long>::max();
unsigned long long fres = 0;
for(int i = 0; i < 100; i++) {
auto t1 = std::chrono::high_resolution_clock::now();
fres = function();
auto t2 = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count();
if(duration < mintime) {
mintime = duration;
}
}
std::cout << message << fres << " " << mintime << std::endl;
}
int main(int argc, char* argv[]) {
timeit(functionA, "functionA: ");
timeit(functionB, "functionB: ");
timeit(functionC, "functionC: ");
timeit(functionH, "functionH: ");
timeit(functionA, "functionA: ");
timeit(functionB, "functionB: ");
timeit(functionC, "functionC: ");
timeit(functionH, "functionH: ");
timeit(functionA, "functionA: ");
timeit(functionB, "functionB: ");
timeit(functionC, "functionC: ");
timeit(functionH, "functionH: ");
return 0;
}