Reputation: 397
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
Upvotes: 21
Views: 2718
Reputation: 29451
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!
Upvotes: 33
Reputation: 10417
||
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();
Upvotes: 2
Reputation: 283733
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
.
Upvotes: 3
Reputation: 4242
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.
Upvotes: 7
Reputation: 1734
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.
Upvotes: 1
Reputation: 10349
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).
Upvotes: 2