none
Switch Statement vs. If-else if-else

    Question

  • Hi Guys!

    Will I get better performance if I use a switch statement instead of If, else if and else statements?

    ~Matt

    Sunday, October 29, 2006 12:15 AM

All replies

  • You should use switch statements if you have multiple choices. It also makes your code easier to read.

    There are lots of things that a switch statement can't be used for bit as a general rule if a switch statement can be used and you have 3 or more cases then a switch statement is a better choice. In today's modern world, the performance difference is actually negligible. You will usually have more performance problems due to creating unnecessary objects or increaseing referential integrity, or etc.

    Sunday, October 29, 2006 12:38 AM
  • well apart from making the code more readable, it is apperent that the case statement will be faster than an if else statement. I found this to be interesting and think it may interest you:

    http://blogs.msdn.com/peterhal/archive/2005/08/12/451124.aspx

     

    Sunday, October 29, 2006 12:41 AM
  •  ahmedilyas wrote:
    the case statement will be faster than an if else statement.

    That turns out not always to be the case (if you'll pardon the pun).

    First, lets be clear that there are many uncertainties to be considered - the IL generated from the statement(s) in question, the native opcodes the IL is translated into, the capabilities of the hardware those opcodes are executing upon and so forth.  Even when we know the answer to some of these questions today, our assumptions might be invalidated by future changes in technologies such as branch prediction, instruction pipelining etc.

    Even if we just consider the IL generated from a Switch statement, we can't say that the code will always be significantly more efficient than a sequence of if-else statements.

    Let's look at the best possible scenario, an integer switch variable and a compact set of case values:


    switch (i)
    {
       case 1:
          j = 9;
          break;
       case 2:
          j = 8;
          break;
       case 4:
          j = 7;
          break;
       default:
          j = 6;
          break;
    }

     

    The Intermediate Language code generated for such a case statement looks like this.


    //000054:             switch (i)
        IL_003d:  ldloc.0
        IL_003e:  stloc.s CS$4$0001
        IL_0040:  ldloc.s CS$4$0001
        IL_0042:  ldc.i4.1
        IL_0043:  sub
        IL_0044:  switch (
                              IL_005b,
                              IL_0060,
                              IL_0068,
                              IL_0064)
        IL_0059:  br.s IL_0068

    //000055:             {
    //000056:                 case 1:
    //000057:                     j = 9;
        IL_005b:  ldc.i4.s 9
        IL_005d:  stloc.1
    //000058:                     break;
        IL_005e:  br.s IL_006c

    //000059:                 case 2:
    //000060:                     j = 8;
        IL_0060:  ldc.i4.8
        IL_0061:  stloc.1
    //000061:                     break;
        IL_0062:  br.s IL_006c

    //000062:                 case 4:
    //000063:                     j = 7;
        IL_0064:  ldc.i4.7
        IL_0065:  stloc.1
    //000064:                     break;
        IL_0066:  br.s IL_006c

    //000065:                 default:
    //000066:                     j = 6;
        IL_0068:  ldc.i4.6
        IL_0069:  stloc.1
    //000067:                     break;
        IL_006a:  br.s IL_006c

     

    That code is pretty efficient.  It generates a table based on the possible case values, indexes that table by the switch value and executes the logic at the address retrieved from the table.  It is even smart enough to fill the gap between Case 2 and Case 4 by putting the 'default' address (IL_0068) in the slot that would otherwise belong to Case 3. 

    The time to execute the Switch part is pretty much linear.  It doesn't matter what value the switch variable has, dialing out the relevant address from the table takes the same amount of time.  Falling through the table to find other (default) values presumably doesn;t take much longer, so the switch statement is going to execute more or less in O(1) time.

    Now, let's look at the same Switch statement if the three case values are substantially further apart.


    //000070:             switch (i)
        IL_006c:  ldloc.0
        IL_006d:  stloc.s CS$4$0001
        IL_006f:  ldloc.s CS$4$0001
        IL_0071:  ldc.i4.1
        IL_0072:  beq.s IL_0088

        IL_0074:  ldloc.s CS$4$0001
        IL_0076:  ldc.i4 0x7d0
        IL_007b:  beq.s IL_008d

        IL_007d:  ldloc.s CS$4$0001
        IL_007f:  ldc.i4 0x2dc6c0
        IL_0084:  beq.s IL_0091

        IL_0086:  br.s IL_0095

    //000071:             {
    //000072: case 1:
    //000073:                     j = 9;
        IL_0088:  ldc.i4.s 9
        IL_008a:  stloc.1
    //000074:                     break;
        IL_008b:  br.s IL_0099

    //000075:                 case 2000:
    //000076:                     j = 8;
        IL_008d:  ldc.i4.8
        IL_008e:  stloc.1
    //000077:                     break;
        IL_008f:  br.s IL_0099

    //000078:                 case 3000000:
    //000079:                     j = 7;
        IL_0091:  ldc.i4.7
        IL_0092:  stloc.1
    //000080:                     break;
        IL_0093:  br.s IL_0099

    //000081:                 default:
    //000082:                     j = 6;
        IL_0095:  ldc.i4.6
        IL_0096:  stloc.1
    //000083:                     break;
        IL_0097:  br.s IL_0099

    //000084:             }

     

    Now things are looking a lot more like a sequence of if - else statelemts.  We load the switch value to the stack  then load the constant 1 and branch if the two values are equal.  If not we load the switch value and load the next constant value (2000) and branch if equal.  If not we load the switch value and load the next constant  and so on and so on.  It turns out the code is still slightly more efficient than the corresponding if-else chain but we've lost a lot of the efficiencies we got from the branch table approach. 

    Now the switch statement is going to execute in O(n) time.  For low values of n that may not be a significant difference but it is there.

    Now let's look at what happens if we use strings as our switch variables


    //000088:             switch (si)
        IL_009f:  ldloc.2
        IL_00a0:  stloc.s CS$4$0002
        IL_00a2:  ldloc.s CS$4$0002
        IL_00a4:  brfalse.s IL_00df

        IL_00a6:  ldloc.s CS$4$0002
        IL_00a8:  ldstr "one"
        IL_00ad:  call bool [mscorlib]System.String::op_Equality(string, string)
        IL_00b2:  brtrue.s IL_00d2

        IL_00b4:  ldloc.s CS$4$0002
        IL_00b6:  ldstr "two"
        IL_00bb:  call bool [mscorlib]System.String::op_Equality(string, string)
        IL_00c0:  brtrue.s IL_00d7

        IL_00c2:  ldloc.s CS$4$0002
        IL_00c4:  ldstr "three"
        IL_00c9:  call bool [mscorlib]System.String::op_Equality(string, string)
        IL_00ce:  brtrue.s IL_00db

        IL_00d0:  br.s IL_00df

    //000089:             {
    //000090:                 case "one":
    //000091:                     j = 9;
        IL_00d2:  ldc.i4.s 9
        IL_00d4:  stloc.1
    //000092:                     break;
        IL_00d5:  br.s IL_00e3

    //000093:                 case "two":
    //000094:                     j = 8;
        IL_00d7:  ldc.i4.8
        IL_00d8:  stloc.1
    //000095:                     break;
        IL_00d9:  br.s IL_00e3

    //000096:                 case "three":
    //000097:                     j = 7;
        IL_00db:  ldc.i4.7
        IL_00dc:  stloc.1
    //000098:                     break;
        IL_00dd:  br.s IL_00e3

    //000099:                 default:
    //000100:                     j = 6;
        IL_00df:  ldc.i4.6
        IL_00e0:  stloc.1
    //000101:                     break;
        IL_00e1:  br.s IL_00e3

    //000102:             }

     

    Again we have the if-else chain like structure to the code giving us an O(n) execution time.  On top of that, our test for equality is no longer a call to beq.s but is now a call bool [mscorlib]System.String::op_Equality(string, string) so the cost of each equality test has gone way up. 

    I'm not arguing that if-else-if-else is better, just that for some variations of Switch the performance difference isn't terribly significant. 

    Like MarcD earlier in this thread I would argue that Switch is much more readable (and thus is a good thing).  I'd also argue that ideally you want to construct your switch so that it uses an integer variable and a compact set of case values.

    Sunday, October 29, 2006 5:31 AM
  • That's great Frank, I REALLY appreciate that response - good job. I've never used a case statement, maybe once, so I don't know much about it and can't comment about it apart from knowing how it works in code (the structure/syntax) and the fact that its readable but as for going behind the scenes - I now know what happens :-)
    Sunday, October 29, 2006 3:33 PM
  • something to consider. . . often times, switch statements (as well as convoluted if/else statements) are smelling of a need to refactor.

    are you sure you are not compensating for an underqualified object model???

    In the OOP world if I am using something, do I design for -

    If something is that then

        DoAsThat(something)

    else

        DoAsTheOther(something)

    No. . . I expect something to know how to do whatever it does:

    something.DoWhatEver

    my something implements a common interface and the explicit behavior is defined by the class instantiated.

    google "Replace Conditional With Polymophism" and "Replace Conditional With Vistor" or "Replace Conditional With Strategy"

    Here is a link to an example of the approach (it looks overwhelming only because he was using VB)

    In a nutshell, the original poster was initiating various processes on a CurrencyFund dependent on a Currency enum - US Dollars, Euros, Sterling, etc. . .

    What I show is that if the Currency types are abstracted as a class, the funds can inherit from the Currency type and implement their specific behavior thereby eliminating the conditional processing.

    On a small scale, two or three conditions/ one or two behaviors it may be overkill. but anything over that I would definitely recommend a refactoring.

     

     

    Monday, October 30, 2006 5:19 AM
  • According to the benchmarks in this blog, if/else and the ternary ?: operator perform just as fast as the switch/case construct.

    The exception occurs with nested switch/case statements, where performance starts to lag considerably.

    Thursday, February 06, 2014 6:57 AM