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  
}

      

+3


source to share


2 answers


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.

+4


source


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.

+6


source







All Articles