2

What is the most performant choice between :

if(myConditionA){
    if (myConditionB){
        if (myConditionC){
            //do something
        }
    }
}

and

if(myConditionA && myConditionB && myConditionC){
    //do something
}

What is the best choice and why ? Does it depends on the language ?

EDIT : It's not a question about code quality.

d3cima
  • 729
  • 1
  • 10
  • 31
  • Depends if you have else conditions – OneCricketeer Mar 27 '17 at 13:49
  • just basic if, no else statements for this case – d3cima Mar 27 '17 at 13:51
  • 1
    It also depends on the language. Most, but not all languages have short-cut evaluation. If they don't then the first version is more efficient, as you actually implement the short-cut evaluation. Note that when conditions have side-effects, and no short-cut evaluation exists, there can be a difference in behaviour between the two. – trincot Mar 27 '17 at 13:51
  • matter of taste really. I would use the second notation. – Maurice Perry Mar 27 '17 at 13:51
  • 4
    Possible duplicate of [What is better ? Multiple if statements, or one if with multiple conditions](http://stackoverflow.com/questions/5259938/what-is-better-multiple-if-statements-or-one-if-with-multiple-conditions) – dspano Mar 27 '17 at 14:06
  • @dspano it's not a Java question specific question, and i'm not looking for quality-code response but performance response :) – d3cima Mar 27 '17 at 14:09
  • @trincot I googled and found out there is not a lot of languages with short-cut evaluation :). – d3cima Mar 27 '17 at 14:11
  • Question is not about code quality but this according to me is the fundamental issue why you should choose one over the other [Sonar rule S1066](https://dev.eclipse.org/sonar/rules/show/squid:S1066) – Petter Friberg Mar 27 '17 at 15:40
  • Many languages have short-circuit evaluation. Certainly, most of the C-derived languages do. However, they don't all implement it the same way. C, for example, didn't specify the order of operations, so `if (A && B && C)` might evaluate C first, then A, then B. C#, on the other hand, specifies left-to-right evaluation as well as short-circuiting. – Jim Mischel Mar 27 '17 at 18:05

3 Answers3

6

What happened when you tried it, and why would you expect there to be any difference at all with what the compiler produces? There is no difference in your algorithm between the two.

unsigned int one ( unsigned int myConditionA, unsigned int myConditionB, unsigned int myConditionC )
{
    if(myConditionA){
        if (myConditionB){
            if (myConditionC){
                //do something
                return(1);
            }
        }
    }
    return(0);
}
unsigned int two ( unsigned int myConditionA, unsigned int myConditionB, unsigned int myConditionC )
{
    if(myConditionA && myConditionB && myConditionC){
        //do something
        return(1);
    }
    return(0);
}

00000000 <one>:
   0:   e3510000    cmp r1, #0
   4:   13520000    cmpne   r2, #0
   8:   13a02001    movne   r2, #1
   c:   03a02000    moveq   r2, #0
  10:   e3500000    cmp r0, #0
  14:   03a00000    moveq   r0, #0
  18:   12020001    andne   r0, r2, #1
  1c:   e12fff1e    bx  lr

00000020 <two>:
  20:   e3510000    cmp r1, #0
  24:   13520000    cmpne   r2, #0
  28:   13a02001    movne   r2, #1
  2c:   03a02000    moveq   r2, #0
  30:   e3500000    cmp r0, #0
  34:   03a00000    moveq   r0, #0
  38:   12020001    andne   r0, r2, #1
  3c:   e12fff1e    bx  lr

hah, okay this was ugly but only because of how I wrote the test, had these been one or the other, but not both then the compiler would have produced the same code clearly. So doing a measured performance test on this might show a difference if you were able to see that, but it is a bad test.

00000000 <two>:
   0:   10800006    beqz    $4,1c <two+0x1c>
   4:   00801025    move    $2,$4
   8:   10a00003    beqz    $5,18 <two+0x18>
   c:   00000000    nop
  10:   03e00008    jr  $31
  14:   0006102b    sltu    $2,$0,$6
  18:   00001025    move    $2,$0
  1c:   03e00008    jr  $31
  20:   00000000    nop

00000024 <one>:
  24:   08000000    j   0 <two>
  28:   00000000    nop

another instruction set.

00000000 <_one>:
   0:   1d80 0002       mov 2(sp), r0
   4:   0308            beq 16 <_one+0x16>
   6:   0bf6 0004       tst 4(sp)
   a:   0306            beq 18 <_one+0x18>
   c:   15c0 0001       mov $1, r0
  10:   0bf6 0006       tst 6(sp)
  14:   0301            beq 18 <_one+0x18>
  16:   0087            rts pc
  18:   0a00            clr r0
  1a:   0087            rts pc

0000001c <_two>:
  1c:   1d80 0002       mov 2(sp), r0
  20:   0308            beq 32 <_two+0x16>
  22:   0bf6 0004       tst 4(sp)
  26:   0306            beq 34 <_two+0x18>
  28:   15c0 0001       mov $1, r0
  2c:   0bf6 0006       tst 6(sp)
  30:   0301            beq 34 <_two+0x18>
  32:   0087            rts pc
  34:   0a00            clr r0
  36:   0087            rts pc

different compiler

00000000 <one>:
   0:   e3510000    cmp r1, #0
   4:   13a01001    movne   r1, #1
   8:   e3500000    cmp r0, #0
   c:   13a00001    movne   r0, #1
  10:   e3520000    cmp r2, #0
  14:   e0000001    and r0, r0, r1
  18:   13a02001    movne   r2, #1
  1c:   e0000002    and r0, r0, r2
  20:   e12fff1e    bx  lr

00000024 <two>:
  24:   e3510000    cmp r1, #0
  28:   13a01001    movne   r1, #1
  2c:   e3500000    cmp r0, #0
  30:   13a00001    movne   r0, #1
  34:   e3520000    cmp r2, #0
  38:   e0000001    and r0, r0, r1
  3c:   13a02001    movne   r2, #1
  40:   e0000002    and r0, r0, r2
  44:   e12fff1e    bx  lr

another target with the other compiler

00000000 <one>:
   0:   27bdfff8    addiu   $29,$29,-8
   4:   afbe0004    sw  $30,4($29)
   8:   03a0f025    move    $30,$29
   c:   0005082b    sltu    $1,$0,$5
  10:   0004102b    sltu    $2,$0,$4
  14:   00410824    and $1,$2,$1
  18:   0006102b    sltu    $2,$0,$6
  1c:   00221024    and $2,$1,$2
  20:   03c0e825    move    $29,$30
  24:   8fbe0004    lw  $30,4($29)
  28:   03e00008    jr  $31
  2c:   27bd0008    addiu   $29,$29,8

00000030 <two>:
  30:   27bdfff8    addiu   $29,$29,-8
  34:   afbe0004    sw  $30,4($29)
  38:   03a0f025    move    $30,$29
  3c:   0005082b    sltu    $1,$0,$5
  40:   0004102b    sltu    $2,$0,$4
  44:   00410824    and $1,$2,$1
  48:   0006102b    sltu    $2,$0,$6
  4c:   00221024    and $2,$1,$2
  50:   03c0e825    move    $29,$30
  54:   8fbe0004    lw  $30,4($29)
  58:   03e00008    jr  $31
  5c:   27bd0008    addiu   $29,$29,8

Even unoptimized I got the same result. There is no difference between the two so why would the compiler generate a difference such that might be a performance difference?

old_timer
  • 69,149
  • 8
  • 89
  • 168
  • sure it is possible to come across a compiler that for some reason will go on to evaluate the second (and third) compare if the first is not true and or do the three compares in some way that they dont evaluate them until after, compare, save z flag, compare save z flag, compare save z flag, add/and the z flags. But I would avoid that compiler... – old_timer Mar 27 '17 at 14:30
  • Would have upvoted, but answer sounded very condescending – dwn Mar 27 '17 at 14:41
  • The poster asked a question without making any effort, despite not being a stackoverflow question anyway. Like simply posting your homework verbatim. but this one being much easier than homework to understand. – old_timer Mar 27 '17 at 15:23
  • it will get closed and deleted. in the mean time a few folks will see that they can simply do some experiments on questions like this as they ponder the answer, they have the tools at their disposal. – old_timer Mar 27 '17 at 15:24
  • a stackoverflow question would have been why didnt my compiler produce the same results for these two code segments. – old_timer Mar 27 '17 at 15:25
  • The asker obviously didn't know how it was being compiled or how to check it, which is why they asked. It was a great question for finding out, and also maybe getting some insight on compilers and compiler history from an old timer. – dwn Mar 27 '17 at 16:33
  • I leave it as an exercise to the reader as to what instruction sets I used, and will admit for one of them I did get different results with -O2, and that would be an interesting research project, but with -O3 with that target then both matched. the one that had one function call the other was very interesting, not sure I have seen a compiler do that, so that was also an interesting side effect of this question. by itself not an interesting SO question, as it would mean just dig into the source of the compiler backend, clearly someone added that optimization. if one would call it that. – old_timer Mar 27 '17 at 17:29
2

If you have something like that the best way to go is by avoiding arrow code like that:

if (!myConditionA)
    return;
if (!myConditionB)
    return;
if (!myConditionC)
    return;
//do something

You can also throw all the condition into another function and use it like:

if (!aggergateCondition())
    return;
// do something

It makes your code clean and readable and reduces the amount of indents, flattening the structure.

EDIT:

The better performing way is theoretically the form with the && statements. Why? Because it does not branch and it's lazy. The if statements are kind of lazy too, but they branch.

But this does not have to hold necessarily. You have to take branch prediction into account which might omit some of these if statements and could break more often at the && operator (omitting just one if statement is far easier). There could be also the possibility of some optimizations performed by the compiler which might lead to the fact that both of these if cases would result in the same assembly or byte code.

So basically - it's hard to tell which one has better performance, because of the magic which is happening behind the curtains of compiler optimizations and even CPU architecture.

If you want some premature optimization tutorial I recommend this book to you :)

Community
  • 1
  • 1
Adrian Jałoszewski
  • 1,695
  • 3
  • 17
  • 33
  • of course, i would aggregate my conditions but It's not a question about code quality :) it's more a question about fastest code. – d3cima Mar 27 '17 at 13:58
  • Added some "bad" literature recommendation for you. – Adrian Jałoszewski Mar 27 '17 at 14:17
  • Saying that using `&&` doesn't branch is incorrect. If you look at the generated code, you'll see that it does in fact branch. In many (most?) languages, using the `&&` operator will result in a branch if the language does short-circuit evaluation. When you have `if(A && B && C)`, and `A` turns out to be false, there will be a branch around evaluation of `B` and `C`. – Jim Mischel Mar 27 '17 at 18:13
1

There is little difference between the short circuit && and the nested ifs in compiled code, but there are still ways to optimize this situation. It depends on the likelihood of each condition. This can be determined through profile guided optimization or you can try and empirically determine the statistics.

If each boolean condition has the same approximate likelihood, then order doesn't matter; however if one or more has a greater chance of being false then they should be ordered from most likely to be false to most likely to be true. If all of them are fairly likely to be true (multiplying all the probabilities of being true yields >= 0.5) then using the bitwise & operator instead of the logical && operator will eliminate typically unnecessary branch conditions.

There are always exceptions to this, such as if one of the conditions is a call to a relatively slow function (not just a stored boolean value), for example if ((*d == *s) && !strcmp(d,s)){ ... }

technosaurus
  • 7,676
  • 1
  • 30
  • 52