2

Is there any (possible) performance difference between following switch statements or they are completely equivalent?

I've not found any significative difference when profiling them, but I'm still curious if any compiler/flag may improve it if using one or another.

// f, g, h can be functions such as int f(int)

int choose1(int x) {
  switch (x) {
  case 0: return f(x);
  case 2: return g(x);
  case 5: return h(x);
  }
  return x;
}

int choose2(int x) {
  switch (x) {
  case 0:  return f(x);
  case 2:  return g(x);
  case 5:  return h(x);
  default: return x;
  }
}

What about if case statements are not so simple, like:

int choose3(int x) {
  switch (x) {
  case 0: /* some actions1 */; return f(x);
  case 2: /* some actions2 */; return g(x);
  case 5: /* some actions3 */; return h(x);
  }
  /* more actions */
  return x;
}

int choose4(int x) {
  switch (x) {
  case 0:  /* some actions1 */; return f(x);
  case 2:  /* some actions2 */; return g(x);
  case 5:  /* some actions3 */; return h(x);
  default: /* more actions  */; return x;
  }
}

Even when searched around for a while I've only found posts related to the single exit-point (very common in C) or just talking about programming practices (maintenance, readability...). A similar question is this one related to the if statement, but all answers basically end discussing early return convenience (programming practice) rather than actual performance differences.

I'm not interested now in such programming practices, although I'll be glad to know your contributions in comments.

Community
  • 1
  • 1
cbuchart
  • 10,847
  • 9
  • 53
  • 93
  • Why should it be different? ... Because compilers are totally stupid today. :) If in doubt then look at the generated assembly code ... online. – knivil May 11 '17 at 16:05
  • @knivil nice idea, I did just that, see my answer and let me know about your thoughts! – gsamaras May 11 '17 at 16:15
  • One issue is that it violates the "one entry one exit" rule of many Coding Guidelines (such as MISRA). – Thomas Matthews May 11 '17 at 16:17
  • @Thomas Matthews, that rule has not been in use for decades. Compilers have evolved since 1975. There is nothing wrong with returning from multiple places within a function or method in 2017. It in fact, makes if/else statements much more maintainable in many cases to check for error, handle and return, rather than write a giant if else block. – Christopher Pisz May 11 '17 at 16:28
  • @ChristopherPisz: See MISRA C 2004, rule 14.7 "A function shall have a single point of exit at the end of the function." – Thomas Matthews May 11 '17 at 19:35
  • @ChristopherPisz: The content of the rule reads: This is required by IEC 61508, under good programming style." – Thomas Matthews May 11 '17 at 19:39
  • I've seen it and it is without basis in 2017. Why should I care what the "Motor Industry Software Reliability Association" thought was good practice, for C rather than C++, in 2004? I could point you to any number of documents on style and practice by any number of organizations that are chalk full of incorrect information and bad practice. – Christopher Pisz May 12 '17 at 14:21
  • 1
    See https://softwareengineering.stackexchange.com/questions/118703/where-did-the-notion-of-one-return-only-come-from – Christopher Pisz May 12 '17 at 14:26

1 Answers1

3

This is a premature optimization worry.

Both are equally efficient.

If you want, you can generate the machine instructions and look that into, but a switch statement is usally a series of if statements.

As a result, branch prediction is what will be likely to be the bottleneck here, so a simple optimization approach is to place in the first case the condition that is most likely to succeed (so that you don't have to evaluate the rest of them).

You can read Efficient switch statement and Default in Switch case for more.


PS - In your case you are returning an int, so no big deal, but if the return type was a class or something that can be big, returning by value is costly. But RVO should take care of that.


Update:

In this Compiler Explorer you can see the assembly:

choose1(int):
        push    rbp
        mov     rbp, rsp
        sub     rsp, 8
        mov     DWORD PTR [rbp-4], edi
        mov     eax, DWORD PTR [rbp-4]
        cmp     eax, 2
        je      .L9
        cmp     eax, 5
        je      .L10
        test    eax, eax
        jne     .L13
        mov     eax, DWORD PTR [rbp-4]
        mov     edi, eax
        call    f(int)
        jmp     .L12
.L9:
        mov     eax, DWORD PTR [rbp-4]
        mov     edi, eax
        call    g(int)
        jmp     .L12
.L10:
        mov     eax, DWORD PTR [rbp-4]
        mov     edi, eax
        call    h(int)
        jmp     .L12
.L13:
        mov     eax, DWORD PTR [rbp-4]
.L12:
        leave
        ret
choose2(int):
        push    rbp
        mov     rbp, rsp
        sub     rsp, 8
        mov     DWORD PTR [rbp-4], edi
        mov     eax, DWORD PTR [rbp-4]
        cmp     eax, 2
        je      .L16
        cmp     eax, 5
        je      .L17
        test    eax, eax
        jne     .L20
        mov     eax, DWORD PTR [rbp-4]
        mov     edi, eax
        call    f(int)
        jmp     .L19
.L16:
        mov     eax, DWORD PTR [rbp-4]
        mov     edi, eax
        call    g(int)
        jmp     .L19
.L17:
        mov     eax, DWORD PTR [rbp-4]
        mov     edi, eax
        call    h(int)
        jmp     .L19
.L20:
        mov     eax, DWORD PTR [rbp-4]
.L19:
        leave
        ret

and as expected see that the code is the same.

Community
  • 1
  • 1
gsamaras
  • 71,951
  • 46
  • 188
  • 305
  • I used ints precisely to avoid discussion avoid construction/copy and the single-exit point. – cbuchart May 11 '17 at 16:04
  • Also, it is more a curiosity about actual differences rather than an optimization issue (as I said, profiling doesn't show any significative difference) – cbuchart May 11 '17 at 16:05
  • That's why I mentioned that as a PS @cbuchart, hmm I updated my answer, hope it serves you better now! =) Nice question BTW, +1! – gsamaras May 11 '17 at 16:06
  • This *might* be premature optimization. If the OP is working in a bottleneck, he may have a good reason to look for optimization possibilities. Also, a `switch` statement is not necessarily implemented as a series of `if` statements. – Andy Thomas May 11 '17 at 16:07
  • @AndyThomas he doesn't care though..Hmm you are right, updated! cbuchart I updated my answer with assembly that answers your question, I believe! – gsamaras May 11 '17 at 16:15
  • 1
    Thanks a lot for your time and your useful answer! Also, I didn't know the Compiler Explorer, I'll take a look at it for future curiosity questions ;) – cbuchart May 11 '17 at 16:18
  • Just to point out and completely close the question: I've tested there with several compilers and all returns identical code for all the examples (well, lacking VS one, but I suppose it will do the same). – cbuchart May 11 '17 at 16:20
  • 1
    @cbuchart VS is also available on godbolt. – Angew is no longer proud of SO May 11 '17 at 16:44