21

I was just wondering what happens inside of an "if OR" and "if AND". I have a feeling that it's just syntactic sugar to use && and || and internally all cases are build as single if statements.

Compact form ||:

if(a || b || c)
{
    DoSomething();
}

Potential internal form:

if(a)
{
    DoSomething();
}    
else if(b)
{
    DoSomething();
}    
else if(c)
{
    DoSomething();
}

Compact form &&:

if(a && b && c)
{
    DoSomething();
}

Potential internal form:

if(a)
{
    if(b)
    {
        if(c)
        {
            DoSomething();
        }
    }
}

Is there any difference in the performance of these two examples?

*Edit: Added the else to the || case

Thomas Ayoub
  • 29,063
  • 15
  • 95
  • 142
hbertsch
  • 367
  • 2
  • 12
  • 2
    Why not check if yourself? – Dmitry Mar 19 '15 at 09:52
  • 14
    `||` and `&&` are [short-circuit](http://en.wikipedia.org/wiki/Short-circuit_evaluation) by the way. Also http://ericlippert.com/2012/12/17/performance-rant/ – Soner Gönül Mar 19 '15 at 09:52
  • 21
    Strictly speaking, your interpretation of `||` is wrong - the code you provided will have `DoSomething();` executed three times. – ttaaoossuuuu Mar 19 '15 at 09:54
  • 1
    @SonerGönül What a fantastic link to Mr Lippert, expresses all my frustrations with the unhealthy fixation around performance and C# applications. I will keep hold of that for the next "perf" question I see :-) – Adam Houldsworth Mar 19 '15 at 09:56
  • 2
    Consider that `a`, `b` and `c` are expensive methods that return a `bool` after a long time. Then it's clear why the short-circuiting operator should be preferred over consecutive `if`s. But even they are not methods but `bool` variables `DoSomething()` gets executed three times instead of one if all are true which is something completely different. – Tim Schmelter Mar 19 '15 at 09:57
  • thx you are right. I haven't realized that DoSomething(); will be executed three times in the case of ||. @SonerGönul: cool link about short-circuit! I didn't know about that – hbertsch Mar 19 '15 at 10:10
  • 3
    This question in its general form (it's asked a lot, e.g. "should I use memcpy for structs?") usually comes down to "can I be sure that the language knows how to implement *its own builtin features* in the best way?". Why do you think it would not? – Alex Celeste Mar 19 '15 at 15:33

6 Answers6

33

First of all, || and && are short-circuit. Which mean that in:

if(a || b || c)
    DoSomething();

if a is true, b and c will not be evaluated.

Secondly, your implementation of the || is false:

if(a)
    DoSomething();
if(b)
    DoSomething();
if(c)
    DoSomething();

DoSomething() will be called up to 3 times.

It should be:

if(a)
    DoSomething();
else if(b)
    DoSomething();
else if(c)
    DoSomething();

To finish, if you want performance prefer shorter call first in your conditions:

if(aShortFunctionToExecute() || aVeryVeryLongFunctionToExecute())
     DoSomething();

Will be faster than

if(aVeryVeryLongFunctionToExecute() || aShortFunctionToExecute())
     DoSomething();

Because of lazy-evaluation


If you disassemble the code of:

private static void Main()
{
    if (a() && b() && c())
    {
        Console.WriteLine("DoSomething");
    }
}
bool a(){
    return true;
}
bool b(){
    return 3 % 2 == 1;
}
bool c(){
    return (3 % 2) / 1 == 1;
}

You'll get:

    if (a() && b() && c())
00000022  call        FFFFFFFFFFEE8D90 
00000027  mov         byte ptr [rbp+20h],al 
0000002a  movzx       eax,byte ptr [rbp+20h] 
0000002e  test        eax,eax 
00000030  je          000000000000005A 
00000032  call        FFFFFFFFFFEE8D98 
00000037  mov         byte ptr [rbp+21h],al 
0000003a  movzx       eax,byte ptr [rbp+21h] 
0000003e  test        eax,eax 
00000040  je          000000000000005A 
00000042  call        FFFFFFFFFFEE8DA0 
00000047  mov         byte ptr [rbp+22h],al 
0000004a  movzx       ecx,byte ptr [rbp+22h] 
0000004e  xor         eax,eax 
00000050  test        ecx,ecx 
00000052  sete        al 
00000055  mov         dword ptr [rbp+24h],eax 
00000058  jmp         0000000000000062 
0000005a  nop 
0000005b  mov         dword ptr [rbp+24h],1 
00000062  nop 
00000063  movzx       eax,byte ptr [rbp+24h] 
00000067  mov         byte ptr [rbp+2Fh],al 
0000006a  movzx       eax,byte ptr [rbp+2Fh] 
0000006e  test        eax,eax 
00000070  jne         0000000000000087 
        {
00000072  nop 
            Console.WriteLine("DoSomething");
00000073  mov         rcx,12603398h 
0000007d  mov         rcx,qword ptr [rcx] 
00000080  call        00000000577A82A0 
00000085  nop 
        }

and for the code:

private static void Main()
{
    if (a())
        if(b())
            if(c())
                Console.WriteLine("DoSomething");
}
static bool a(){
    return true;
}
static bool b(){
    return 3 % 2 == 1;
}
static bool c(){
    return (3 % 2) / 1 == 1;
}

You'll get:

if (a())
00000022  call        FFFFFFFFFFEE8D90 
00000027  mov         byte ptr [rbp+20h],al 
0000002a  movzx       ecx,byte ptr [rbp+20h] 
0000002e  xor         eax,eax 
00000030  test        ecx,ecx 
00000032  sete        al 
00000035  mov         dword ptr [rbp+24h],eax 
00000038  movzx       eax,byte ptr [rbp+24h] 
0000003c  mov         byte ptr [rbp+3Fh],al 
0000003f  movzx       eax,byte ptr [rbp+3Fh] 
00000043  test        eax,eax 
00000045  jne         00000000000000A4 
            if(b())
00000047  call        FFFFFFFFFFEE8D98 
0000004c  mov         byte ptr [rbp+28h],al 
0000004f  movzx       ecx,byte ptr [rbp+28h] 
00000053  xor         eax,eax 
00000055  test        ecx,ecx 
00000057  sete        al 
0000005a  mov         dword ptr [rbp+2Ch],eax 
0000005d  movzx       eax,byte ptr [rbp+2Ch] 
00000061  mov         byte ptr [rbp+3Fh],al 
00000064  movzx       eax,byte ptr [rbp+3Fh] 
00000068  test        eax,eax 
0000006a  jne         00000000000000A4 
                if(c())
0000006c  call        FFFFFFFFFFEE8DA0 
00000071  mov         byte ptr [rbp+30h],al 
00000074  movzx       ecx,byte ptr [rbp+30h] 
00000078  xor         eax,eax 
0000007a  test        ecx,ecx 
0000007c  sete        al 
0000007f  mov         dword ptr [rbp+34h],eax 
00000082  movzx       eax,byte ptr [rbp+34h] 
00000086  mov         byte ptr [rbp+3Fh],al 
00000089  movzx       eax,byte ptr [rbp+3Fh] 
0000008d  test        eax,eax 
0000008f  jne         00000000000000A4 
                    Console.WriteLine("DoSomething");
00000091  mov         rcx,125D3398h 
0000009b  mov         rcx,qword ptr [rcx] 
0000009e  call        00000000577B82A0 
000000a3  nop 

Which is a bit longer: it takes 40 instructions instead of 31.


As pointed out by thanosqr, the performance also depend of the probability for your condition to be true. To take his example:

If a fails 99% of the time and take 1 sec to run and if b succeed 99% of the time and take 10 sec to run, over 100 tries you'll be faster putting b first:

if(b || a) => 10s 99% ==> 100 runs will take 99*10+11 = 1001s
if(b || a) => 11s 1%

if(a || b) => 11s 99% ==> 100 runs will take 99*11+1 = 1090s
if(a || b) => 1s 1%

Also, I suggest you this reading " Why is it faster to process a sorted array than an unsorted array? " which is quite interesting!

Community
  • 1
  • 1
Thomas Ayoub
  • 29,063
  • 15
  • 95
  • 142
  • 4
    "It's 25% faster to use the compact way." No, it's not. It takes 25% less instructions. The number of instructions is no indication of how fast it will run, and it will vary wildly depending on many many different parameters. – Falanwe Mar 19 '15 at 13:04
  • 5
    I would say that performance also depends on chance of short-circuiting; e.g.: if the fast check takes 1sec and fails 99 times out of 100 while the slow check takes 10sec but fails only 1 time, (slow || fast) would take 1001sec while (fast || slow) would take 1090 sec. the extreme case would be a check that's always false, even if it's super fast it's better to check the slow one first. – Thanos Tintinidis Mar 19 '15 at 14:19
  • 1
    Please clarify definition of "IL". – Thomas Matthews Mar 19 '15 at 21:37
  • @Thomas Intermediate language? MSIL? Please look at the answer below. – Tachyon Mar 20 '15 at 12:37
7

Using the compact form, the IL emitted by the C# compiler will be less verbose, resulting in less instructions to be handled at runtime. The emitted IL statements and their logic are actually the same, so there's no fancy built-in support to handle that case or some special instruction (keep in mind that you can put any expression with a boolean result into an if).

For the compact form using the || operator (debug build):

.method private hidebysig static void  One() cil managed
{
  // Code size       38 (0x26)
  .maxstack  2
  .locals init ([0] bool CS$4$0000)
  IL_0000:  nop
  IL_0001:  ldsfld     bool ConsoleApplication4.Program::a
  IL_0006:  brtrue.s   IL_0019
  IL_0008:  ldsfld     bool ConsoleApplication4.Program::b
  IL_000d:  brtrue.s   IL_0019
  IL_000f:  ldsfld     bool ConsoleApplication4.Program::c
  IL_0014:  ldc.i4.0
  IL_0015:  ceq
  IL_0017:  br.s       IL_001a
  IL_0019:  ldc.i4.0
  IL_001a:  nop
  IL_001b:  stloc.0
  IL_001c:  ldloc.0
  IL_001d:  brtrue.s   IL_0025
  IL_001f:  call       void ConsoleApplication4.Program::DoSomething()
  IL_0024:  nop
  IL_0025:  ret
} // end of method Program::One

With your internal form (considering you're using else if instead of an if):

.method private hidebysig static void  Two() cil managed
{
  // Code size       60 (0x3c)
  .maxstack  2
  .locals init ([0] bool CS$4$0000)
  IL_0000:  nop
  IL_0001:  ldsfld     bool ConsoleApplication4.Program::a
  IL_0006:  ldc.i4.0
  IL_0007:  ceq
  IL_0009:  stloc.0
  IL_000a:  ldloc.0
  IL_000b:  brtrue.s   IL_0015
  IL_000d:  call       void ConsoleApplication4.Program::DoSomething()
  IL_0012:  nop
  IL_0013:  br.s       IL_003b
  IL_0015:  ldsfld     bool ConsoleApplication4.Program::b
  IL_001a:  ldc.i4.0
  IL_001b:  ceq
  IL_001d:  stloc.0
  IL_001e:  ldloc.0
  IL_001f:  brtrue.s   IL_0029
  IL_0021:  call       void ConsoleApplication4.Program::DoSomething()
  IL_0026:  nop
  IL_0027:  br.s       IL_003b
  IL_0029:  ldsfld     bool ConsoleApplication4.Program::c
  IL_002e:  ldc.i4.0
  IL_002f:  ceq
  IL_0031:  stloc.0
  IL_0032:  ldloc.0
  IL_0033:  brtrue.s   IL_003b
  IL_0035:  call       void ConsoleApplication4.Program::DoSomething()
  IL_003a:  nop
  IL_003b:  ret
} // end of method Program::Two

So there are way more instructions to handle all the jumps required by the additional if statements. The first form is therefore more efficient (and actually more readable :)).

In terms of performance (each method measured 10 times with 10.000.000 iterations and removed the highest and lowest values, release build):

Compact form: 55ms in average

Verbose form: 56ms in average

So there's no big difference at all.

Gene
  • 4,192
  • 5
  • 32
  • 56
3

For those who read C# better than assembly, the real internal forms are closer to:

if(a) goto yes;
if(b) goto yes;
if(c) goto yes;
goto no;
yes:  DoSomething();
goto done;
no:   /* if there were an else it would go here */;
done: ;

for

if(a || b || c)
  DoSomething();

and

if(!a) goto no;
if(!b) goto no;
if(!c) goto no;
yes:  DoSomething();
goto done;
no:   /* if there were an else it would go here */;
done: ;

for

if(a && b && c)
  DoSomething();

That's because the actual instructions are conditional branches -- in the internal form it is not possible for an if to be associated with a block, a nested if, or actually anything except a goto.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
2

The code:

if(a)
    if(b)
        if(c)
            DoSomething();

is a logical (but not "practical") equivalent for:

if(a && b && c)
    DoSomething();

As for the OR operator you've got it a bit wrong. A logical (but, again, not "practical") equivalent for:

if(a || b || c)
    DoSomething();

would be:

if(a)
    DoSomething();
else if(b)
    DoSomething();
else if(c)
    DoSomething();

By practical difference I understand any resulting code differences introduced by the compiler (refer to other answers for details).

Grx70
  • 10,041
  • 1
  • 40
  • 55
2

|| and && are conditional-operators. They're also operators, like other operators you might know. (e.g. +, *, ...)

Their behavior is similar to logical-operators, | and &. They receive two bool type variables and return bool value in this way:

// If one of them is true, the return value is true. Otherwise, it's false.
true  | true  == true
true  | false == true
false | true  == true
false | false == false
// If both of them are true, the return value is true. Otherwise, it's false.
true  & true  == true
true  & false == false
false & true  == false
false & false == false

However, as for conditional-operators, there's a bit difference: short-circuit.

Suppose this code:

bool func1() { .. }
bool func2() { .. }

bool b1 = func1() || func2();
bool b2 = func1() && func2();

If func1() returns true, b1 becomes true regardless of what func2() returns. Therefore, we don't need to call func2() and actually don't. If func1() returns false, the same thing is applied to b2. This behavior is called short-circuit.


Now, let's think about your example.

if (a || b || c)
    DoSomething();

It's equal to

bool value = a || b || c;
if (value)
    DoSomething();

Since the order of evaluation of conditional operators is left-to-right, it's equal to

bool value = (a || b) || c;
if (value)
    DoSomething();
Οurous
  • 348
  • 6
  • 17
ikh
  • 10,119
  • 1
  • 31
  • 70
1

Their VB equivalents can be more describing. || is OrElse and && is AndAlso in VB.
These are conditional operators; meaning they make the control clause - if in your case - evaluate the conditions as needed and not all of them always.

For example, in if ( a || b ) if a is true, it doesn't matter what b is; the result is true and therefore b will not get evaluated and this will result in faster execution.

This feature can be used as a null-checking mechanism too. if ( a != null && a.prop == somevalue ) will prevent a Null Reference Exception if a is null and if it's not null, its prop property will be accessed to evaluate the second condition.

Achilles
  • 1,554
  • 1
  • 28
  • 36