Does the compiler optimize repeated identical checks
I came across some code that checks the same condition repeatedly. It seems like C # 6 will free us from this ugly redundant code, but at the same time, is there any benefit to introducing a bool variable, or is the compiler smart enough to sort this out for us and not repeat the same thing over again and again? (even though we do check, am I guessing the delay in bool will be (marginally) faster?)
// here we're doing the same check over and over again
string str1 = (CustomerData == null) ? string.Empty : CustomerData.str1;
string str2 = (CustomerData == null) ? string.Empty : CustomerData.str2;
string str3 = (CustomerData == null) ? string.Empty : CustomerData.str3;
// ... rinse and repeat
// here we're still doing a check, but against a boolean variable
bool is_valid = CustomerData == null;
string str1 = is_valid ? string.Empty : CustomerData.str1;
string str2 = is_valid ? string.Empty : CustomerData.str2;
string str3 = is_valid ? string.Empty : CustomerData.str3;
// ... rinse and repeat
In this case, it may not be critical, but what happens if you are comparing 2 objects, which then have to go through and check all the fields inside them?
Note: since this is inside a method, I couldn't rely on the default for strings ( null
), so the workaround creates all the strings by initializing them to string.Empty, and then do something like:
if (CustomerData != null) {
// set all of the above strings again, changing from empty to actual values
}
source to share
I guess we should be specific about which compiler. Two compilers are considered: C # (source -> MSIL) and JITter (MSIL -> native)
No, the Microsoft C # compiler does not rewrite this code to optimize multiple checks. In my experience, the C # compiler does a little optimization (for some reason), and MSIL does the middleware in the traditional compiler chaining.
C # code ...
Customer CustomerData = new Customer();
string str1 = (CustomerData == null) ? string.Empty : CustomerData.str1;
string str2 = (CustomerData == null) ? string.Empty : CustomerData.str2;
string str3 = (CustomerData == null) ? string.Empty : CustomerData.str3;
Compiles in MSIL release mode
IL_0006: ldloc.0 // load CustomerData
IL_0007: brfalse.s IL_0012 // if(CustomerData == ) ...
IL_0009: ldloc.0 // load CustomerData
IL_000a: ldfld string ConsoleApplication1.Customer::str1
IL_000f: pop
IL_0010: br.s IL_0018
IL_0012: ldsfld string [mscorlib]System.String::Empty
IL_0017: pop
IL_0018: ldloc.0 // load CustomerData
IL_0019: brfalse.s IL_0024 // if(CustomerData == null) ...
IL_001b: ldloc.0 // load CustomerData
IL_001c: ldfld string ConsoleApplication1.Customer::str2
IL_0021: pop
IL_0022: br.s IL_002a
IL_0024: ldsfld string [mscorlib]System.String::Empty
IL_0029: pop
IL_002a: ldloc.0 // load CustomerData
IL_002b: brfalse.s IL_0036 // if(CustomerData == null) ...
IL_002d: ldloc.0 // load CustomerData
IL_002e: ldfld string ConsoleApplication1.Customer::str3
IL_0033: pop
IL_0034: br.s IL_003c
As for whether a temporary variable performs better, it boils down to being faster:
ldloc
or
ldsfld
Local is faster once, but if JITTER accidentally writes either one of them in the register, it won't make any difference.
Keep in mind that MSIL is good to see what's going on, but doesn't mean JITTER won't do more optimizations (I think we can assume it actually does more) to see if we need to dump the code x86 ..
See Part 2 - SimonWhitehead (+1) dumps native x86 / 64 result and we find that JITter is more than just a pretty name for the translation engine - fooobar.com/questions/2173679 / ...
For what it's worth, I wouldn't lose sleep over it anyway, the performance overhead is negligible (2 opcodes per field), just keep the conditions as they are, it makes the code cleaner.
source to share
To expand on colengame's answer .. it seems like in release build the JITter is smart enough to optimize them.
The debug build does all the comparisons and jumps around a lot. Release build (on x64 anyway ..) creates:
; string str1 = (CustomerData == null) ? string.Empty : CustomerData.str1;
call 000000005F64D620
mov rdx,0E7A80733A0h
mov rdx,qword ptr [rdx]
lea rdi,[rbp+10h]
mov rcx,rdi
call 000000005F64D620
mov rdx,0E7A80733A8h
mov rdx,qword ptr [rdx]
lea rbx,[rbp+18h]
mov rcx,rbx
call 000000005F64D620
mov rsi,qword ptr [rsi]
; string str2 = (CustomerData == null) ? string.Empty : CustomerData.str2;
mov rdi,qword ptr [rdi]
; string str3 = (CustomerData == null) ? string.Empty : CustomerData.str3;
mov rbx,qword ptr [rbx]
; string str6 = is_valid ? string.Empty : CustomerData.str3;
mov rbp,qword ptr [rbp+18h]
It seems like it just ignores your code and goes and moves the data to where it knows it should be .. given the result of an identical expression that was evaluated earlier is known at that point in time.
source to share