C++ 切换 if-else 语句的优点

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/97987/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 12:50:23  来源:igfitidea点击:

Advantage of switch over if-else statement

c++if-statementoptimizationswitch-statement

提问by Zing-

What's the best practice for using a switchstatement vs using an ifstatement for 30 unsignedenumerations where about 10 have an expected action (that presently is the same action). Performance and space need to be considered but are not critical. I've abstracted the snippet so don't hate me for the naming conventions.

使用switch语句与使用if语句进行 30 个unsigned枚举的最佳实践是什么,其中大约 10 个具有预期的操作(目前是相同的操作)。需要考虑性能和空间,但不是关键。我已经抽象了这个片段,所以不要因为命名约定而讨厌我。

switchstatement:

switch陈述:

// numError is an error enumeration type, with 0 being the non-error case
// fire_special_event() is a stub method for the shared processing

switch (numError)
{  
  case ERROR_01 :  // intentional fall-through
  case ERROR_07 :  // intentional fall-through
  case ERROR_0A :  // intentional fall-through
  case ERROR_10 :  // intentional fall-through
  case ERROR_15 :  // intentional fall-through
  case ERROR_16 :  // intentional fall-through
  case ERROR_20 :
  {
     fire_special_event();
  }
  break;

  default:
  {
    // error codes that require no additional action
  }
  break;       
}

ifstatement:

if陈述:

if ((ERROR_01 == numError)  ||
    (ERROR_07 == numError)  ||
    (ERROR_0A == numError)  || 
    (ERROR_10 == numError)  ||
    (ERROR_15 == numError)  ||
    (ERROR_16 == numError)  ||
    (ERROR_20 == numError))
{
  fire_special_event();
}

采纳答案by Nils Pipenbrinck

Use switch.

使用开关。

In the worst case the compiler will generate the same code as a if-else chain, so you don't lose anything. If in doubt put the most common cases first into the switch statement.

在最坏的情况下,编译器将生成与 if-else 链相同的代码,因此您不会丢失任何内容。如果有疑问,请将最常见的情况首先放入 switch 语句中。

In the best case the optimizer may find a better way to generate the code. Common things a compiler does is to build a binary decision tree (saves compares and jumps in the average case) or simply build a jump-table (works without compares at all).

在最好的情况下,优化器可能会找到一种更好的方式来生成代码。编译器所做的常见事情是构建一个二叉决策树(在一般情况下保存比较和跳转)或简单地构建一个跳转表(根本不需要比较)。

回答by Mark Ransom

For the special case that you've provided in your example, the clearest code is probably:

对于您在示例中提供的特殊情况,最清晰的代码可能是:

if (RequiresSpecialEvent(numError))
    fire_special_event();

Obviously this just moves the problem to a different area of the code, but now you have the opportunity to reuse this test. You also have more options for how to solve it. You could use std::set, for example:

显然,这只是将问题移到代码的不同区域,但现在您有机会重用此测试。您还有更多关于如何解决它的选项。您可以使用 std::set,例如:

bool RequiresSpecialEvent(int numError)
{
    return specialSet.find(numError) != specialSet.end();
}

I'm not suggesting that this is the best implementation of RequiresSpecialEvent, just that it's an option. You can still use a switch or if-else chain, or a lookup table, or some bit-manipulation on the value, whatever. The more obscure your decision process becomes, the more value you'll derive from having it in an isolated function.

我并不是说这是 RequiresSpecialEvent 的最佳实现,只是它是一个选项。您仍然可以使用 switch 或 if-else 链,或查找表,或对值进行一些位操作,等等。您的决策过程变得越模糊,您将其置于一个孤立的函数中所获得的价值就越大。

回答by paercebal

The switch isfaster.

该开关更快。

Just try if/else-ing 30 different values inside a loop, and compare it to the same code using switch to see how much faster the switch is.

只需在循环中尝试 if/else-ing 30 个不同的值,并使用 switch 将其与相同的代码进行比较,以查看 switch 的速度有多快。

Now, the switch has one real problem: The switch must know at compile time the values inside each case. This means that the following code:

现在,开关有一个真正的问题:开关必须在编译时知道每种情况下的值。这意味着以下代码:

// WON'T COMPILE
extern const int MY_VALUE ;

void doSomething(const int p_iValue)
{
    switch(p_iValue)
    {
       case MY_VALUE : /* do something */ ; break ;
       default : /* do something else */ ; break ;
    }
}

won't compile.

不会编译。

Most people will then use defines (Aargh!), and others will declare and define constant variables in the same compilation unit. For example:

大多数人然后会使用定义(啊!),其他人会在同一个编译单元中声明和定义常量变量。例如:

// WILL COMPILE
const int MY_VALUE = 25 ;

void doSomething(const int p_iValue)
{
    switch(p_iValue)
    {
       case MY_VALUE : /* do something */ ; break ;
       default : /* do something else */ ; break ;
    }
}

So, in the end, the developper must choose between "speed + clarity" vs. "code coupling".

因此,最终,开发人员必须在“速度 + 清晰度”与“代码耦合”之间做出选择。

(Not that a switch can't be written to be confusing as hell... Most the switch I currently see are of this "confusing" category"... But this is another story...)

(并不是说开关不能写得令人困惑……我目前看到的大多数开关都属于这种“令人困惑”的类别……但这是另一个故事……)

Edit 2008-09-21:

bk1eadded the following comment: "Defining constants as enums in a header file is another way to handle this".

Of course it is.

The point of an extern type was to decouple the value from the source. Defining this value as a macro, as a simple const int declaration, or even as an enum has the side-effect of inlining the value. Thus, should the define, the enum value, or the const int value change, a recompilation would be needed. The extern declaration means the there is no need to recompile in case of value change, but in the other hand, makes it impossible to use switch. The conclusion being Using switch will increase coupling between the switch code and the variables used as cases. When it is Ok, then use switch. When it isn't, then, no surprise.

编辑 2008-09-21:

bk1e添加了以下注释:“在头文件中将常量定义为枚举是处理此问题的另一种方法”。

当然是这样。

extern 类型的要点是将值与源解耦。将此值定义为宏、简单的 const int 声明,甚至定义为枚举都有内联值的副作用。因此,如果定义、枚举值或 const int 值发生变化,则需要重新编译。extern 声明意味着在值更改的情况下不需要重新编译,但另一方面,使得无法使用 switch。结论是使用 switch 将增加 switch 代码和用作 case 的变量之间的耦合。当它确定时,然后使用开关。如果不是,那么也就不足为奇了。

.

.

Edit 2013-01-15:

Vlad Lazarenkocommented on my answer, giving a link to his in-depth study of the assembly code generated by a switch. Very enlightning: http://lazarenko.me/switch/

编辑 2013-01-15:

Vlad Lazarenko评论了我的回答,给出了他对开关生成的汇编代码的深入研究的链接。很有启发:http://lazarenko.me/switch/

回答by Alexandra Franks

Compiler will optimise it anyway - go for the switch as it's the most readable.

编译器无论如何都会优化它 - 选择开关,因为它是最易读的。

回答by scubabbl

The Switch, if only for readability. Giant if statements are harder to maintain and harder to read in my opinion.

开关,如果只是为了可读性。在我看来,巨大的 if 语句更难维护且更难阅读。

ERROR_01: // intentional fall-through

ERROR_01: // 故意掉线

or

或者

(ERROR_01 == numError) ||

(ERROR_01 == numError) ||

The later is more error prone and requires more typing and formatting than the first.

后者更容易出错,并且比第一个需要更多的输入和格式。

回答by Bdoserror

Code for readability. If you want to know what performs better, use a profiler, as optimizations and compilers vary, and performance issues are rarely where people think they are.

可读性代码。如果您想知道什么性能更好,请使用分析器,因为优化和编译器各不相同,而且性能问题很少出现在人们认为的地方。

回答by Martin Beckett

Use switch, it is what it's for and what programmers expect.

使用 switch,这是它的用途,也是程序员所期望的。

I would put the redundant case labels in though - just to make people feel comfortable, I was trying to remember when / what the rules are for leaving them out.
You don't want the next programmer working on it to have to do any unnecessary thinking about language details (it might be you in a few months time!)

不过,我会放入多余的案例标签 - 只是为了让人们感到舒服,我试图记住何时/什么规则将它们排除在外。
您不希望下一个从事它的程序员必须对语言细节进行任何不必要的思考(几个月后可能就是您!)

回答by Peter Cordes

Compilers are really good at optimizing switch. Recent gcc is also good at optimizing a bunch of conditions in an if.

编译器非常擅长优化switch. 最近的 gcc 也擅长优化if.

I made some test cases on godbolt.

我在Godbolt上做了一些测试用例。

When the casevalues are grouped close together, gcc, clang, and icc are all smart enough to use a bitmap to check if a value is one of the special ones.

case值组合在一起时,gcc、clang 和 icc 都足够聪明,可以使用位图来检查值是否是特殊值之一。

e.g. gcc 5.2 -O3 compiles the switchto (and the ifsomething very similar):

例如 gcc 5.2 -O3 编译switch到(和if非常相似的东西):

errhandler_switch(errtype):  # gcc 5.2 -O3
    cmpl    , %edi
    ja  .L5
    movabsq 01325442, %rax   # highest set bit is bit 32 (the 33rd bit)
    btq %rdi, %rax
    jc  .L10
.L5:
    rep ret
.L10:
    jmp fire_special_event()

Notice that the bitmap is immediate data, so there's no potential data-cache miss accessing it, or a jump table.

请注意,位图是直接数据,因此没有潜在的数据缓存未命中访问它或跳转表。

gcc 4.9.2 -O3 compiles the switchto a bitmap, but does the 1U<<errNumberwith mov/shift. It compiles the ifversion to series of branches.

gcc 4.9.2 -O3 将 编译switch为位图,但1U<<errNumber使用 mov/shift 执行。它将if版本编译为一系列分支。

errhandler_switch(errtype):  # gcc 4.9.2 -O3
    leal    -1(%rdi), %ecx
    cmpl    , %ecx    # cmpl , %edi  wouldn't have to wait an extra cycle for lea's output.
              # However, register read ports are limited on pre-SnB Intel
    ja  .L5
    movl    , %eax
    salq    %cl, %rax   # with -march=haswell, it will use BMI's shlx to avoid moving the shift count into ecx
    testl   50662721, %eax
    jne .L10
.L5:
    rep ret
.L10:
    jmp fire_special_event()

Note how it subtracts 1 from errNumber(with leato combine that operation with a move). That lets it fit the bitmap into a 32bit immediate, avoiding the 64bit-immediate movabsqwhich takes more instruction bytes.

请注意它如何从errNumber(withlea将该操作与移动相结合) 中减去 1 。这使它可以将位图装入 32 位立即数,避免movabsq占用更多指令字节的 64 位立即数。

A shorter (in machine code) sequence would be:

更短的(机器代码)序列是:

    cmpl    , %edi
    ja  .L5
    mov     50662721, %eax
    dec     %edi   # movabsq and btq is fewer instructions / fewer Intel uops, but this saves several bytes
    bt     %edi, %eax
    jc  fire_special_event
.L5:
    ret


(The failure to use jc fire_special_eventis omnipresent, and is a compiler bug.)

(使用失败jc fire_special_event无处不在,并且是编译器错误。)

rep retis used in branch targets, and following conditional branches, for the benefit of old AMD K8 and K10 (pre-Bulldozer): What does `rep ret` mean?. Without it, branch prediction doesn't work as well on those obsolete CPUs.

rep ret为了旧的 AMD K8 和 K10(Bulldozer 之前)的利益,在分支目标和条件分支之后使用:`rep ret` 是什么意思?. 没有它,分支预测在那些过时的 CPU 上不能很好地工作。

bt(bit test) with a register arg is fast. It combines the work of left-shifting a 1 by errNumberbits and doing a test, but is still 1 cycle latency and only a single Intel uop. It's slow with a memory arg because of its way-too-CISC semantics: with a memory operand for the "bit string", the address of the byte to be tested is computed based on the other arg (divided by 8), and isn't limited to the 1, 2, 4, or 8byte chunk pointed to by the memory operand.

bt(位测试)与寄存器 arg 很快。它结合了按errNumber位左移 1和执行 a 的工作test,但仍然是 1 个周期的延迟,并且只有一个 Intel uop。由于其过于 CISC 语义,内存 arg 很慢:对于“位字符串”的内存操作数,要测试的字节的地址是基于另一个 arg(除以 8)计算的,并且不是'不限于内存操作数指向的 1、2、4 或 8 字节块。

From Agner Fog's instruction tables, a variable-count shift instruction is slower than a bton recent Intel (2 uops instead of 1, and shift doesn't do everything else that's needed).

Agner Fog 的指令表中,可变计数移位指令比bt最近的 Intel 上的要慢(2 uops 而不是 1,并且 shift 不执行其他所有需要的操作)。

回答by Vineeth Krishna K

Advantages of switch () over if else case are:- 1. Switch is much more efficient compared to if else because each case doesn't depend on previous case unlike if else where individual statement needs to be checked for true or false condition.

switch() 相对于 if else case 的优点是:- 1. 与 if else 相比,switch 效率更高,因为每个 case 不依赖于前一个 case,这与 if else 不同,if else 需要检查单个语句的真假条件。

  1. When there are a no. of values for a single expression, switch case is more flexible over if else because in if else case judgement is based on only two values i.e. either true or false.

  2. Values in switch are user defined whereas values in if else case are based on constraints.

  3. In case of error, statements in switch can be easily cross checked and corrected which is comparatively difficult to check in case of if else statement.

  4. Switch case is much more compact and easy to read and understand.

  1. 当没有。对于单个表达式的值,switch case 比 if else 更灵活,因为在 if else 情况下判断仅基于两个值,即真或假。

  2. switch 中的值是用户定义的,而 if else 中的值基于约束。

  3. 如果出现错误,switch 中的语句可以很容易地进行交叉检查和更正,而如果是 if else 语句则比较难检查。

  4. 开关盒更加紧凑,易于阅读和理解。

回答by Fran?ois

I agree with the compacity of the switch solution but IMO you're hiHymaning the switchhere.
The purpose of the switch is to have differenthandling depending on the value.
If you had to explain your algo in pseudo-code, you'd use an if because, semantically, that's what it is: if whatever_error do this...
So unless you intend someday to change your code to have specific code for each error, I would use if.

我同意交换机解决方案的兼容性,但 IMO 您在这里劫持了交换机
开关的目的是根据值进行不同的处理。
如果你必须用伪代码解释你的算法,你会使用 if ,因为从语义上讲,就是这样:如果whatever_error这样做......
所以除非你打算有一天改变你的代码来为每个错误提供特定的代码,我会使用if