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
|| 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.
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.
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.
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!
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With