编译器是否优化了重复的相同检查

时间:2014-10-28 01:28:25

标签: c# performance compiler-optimization

我遇到了一些反复检查相同条件的代码。好像C#6会让我们摆脱这个丑陋的冗余代码,但与此同时,引入bool变量是否有任何好处,或者编译器是否足够聪明以便为我们排序  而不是反复比较同样的事情一遍又一遍? (即使我们正在进行检查,我会假设将结果藏在一个布尔会(稍微)更快?)

// 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

在这种情况下,这可能并不重要,但如果比较2个需要去的对象并深入检查其中的所有字段会发生什么?

注意:由于这是在方法内,我不能依赖字符串的默认值(null),因此解决方法是创建所有字符串,将它们初始化为 string.Empty,然后做类似的事情:

if (CustomerData != null) {
    // set all of the above strings again, changing from empty to actual values  
}

2 个答案:

答案 0 :(得分:6)

要扩展codenheim的答案......看来,在发布版本中,JITter非常智能,可以优化它们。

Debug构建会进行所有比较并跳转很多。 Release版本(无论如何在x64上)产生:

; 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] 

它似乎只是忽略了你的代码然后将数据移动到知道它应该的位置......给定之前已经评估过的相同表达式的结果是已知的及时。

答案 1 :(得分:4)

我想我们必须具体说明哪个编译器。正在考虑两个编译器,C#(source - > MSIL)和JITter(MSIL - > native)

不,Microsoft C#编译器不会重写此代码以优化多项检查。根据我的经验,C#编译器进行的优化很少(出于某种原因),而MSIL相当于传统编译器链中的中间代码。

C#代码......

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;

在发布模式下编译为MSIL

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

关于临时变量是否更好地工作,它归结为更快的操作:

ldloc

ldsfld

本地更快一次,但如果JITter碰巧将其中任何一个藏在寄存器中,它就不会有所作为。

请记住,MSIL很好地了解正在发生的事情,但并不意味着JITter不会做更多优化(我认为我们可以假设它确实做了更多)所以看看我们需要转储x86代码..

参见第2部分--SimonWhitehead(+1)转储了x86 / 64原生结果,我们发现JITter不仅仅是翻译引擎的漂亮名称 - https://stackoverflow.com/a/26600198/257090

对于它的价值,无论如何我都不会失去睡眠,性能开销可以忽略不计(每个字段2个操作码),只是保持条件不变,它使代码更清洁