Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is happening in an "If(..||..)" and "If(...&&...)" construct internally?

Tags:

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

like image 350
hbertsch Avatar asked Mar 19 '15 09:03

hbertsch


People also ask

What does || mean in an if statement?

|| is a short circuit 'or' boolean operator. If the expression on the left hand side evaluates to true there is no need to evaluate the expression on the right as the condition has already been met.

What if there are two if statements?

Use two if statements if both if statement conditions could be true at the same time. In this example, both conditions can be true. You can pass and do great at the same time. Use an if/else statement if the two conditions are mutually exclusive meaning if one condition is true the other condition must be false.

What does the if condition do?

The IF function is one of the most popular functions in Excel, and it allows you to make logical comparisons between a value and what you expect. So an IF statement can have two results. The first result is if your comparison is True, the second if your comparison is False.


2 Answers

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!

like image 65
Thomas Ayoub Avatar answered Oct 12 '22 23:10

Thomas Ayoub


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.

like image 44
Gene Avatar answered Oct 13 '22 00:10

Gene