bool检查比null检查更快吗?

时间:2014-01-23 14:00:42

标签: c# performance

E.g。我需要提取

bool xIsNull = x == null

来自我检查x == null的循环?

据我所知if (a == true)if (x == null)都使用相同的IL指令。但指针由32位或64位组成。 CLR是否应检查每一位以与null进行比较?

更新
快速测试显示没有区别,但我仍然希望有人解释这一点。

UPDATE2
我使用IL发射,所以我不能指望编译器优化我的代码。只有JIT。

3 个答案:

答案 0 :(得分:9)

记住“过早优化是所有邪恶的根源”,优化的第一条规则是“不要”(第二条,仅针对专业人士,是“不要做它”),这是发生的事情

<强> TL; DR
如果您不想深入研究某些汇编代码,我不会责怪您;)结果表明,使用临时变量不会得到优化并生成更多指令。总而言之,除非您编写非常时间关键任务,否则它不会有任何区别。


考虑以下代码:

string x = null;
bool a = x == null;

if ( a == true ) { Console.WriteLine( ); }
if ( x == null ) { Console.WriteLine( ); }

这是 Debug 模式中生成的IL(我添加了一些注释):

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       46 (0x2e)
  .maxstack  2
  .locals init ([0] string x,
           [1] bool a,
           [2] bool CS$4$0000)
  IL_0000:  nop
  IL_0001:  ldnull
  IL_0002:  stloc.0    // string x = null
  IL_0003:  ldloc.0
  IL_0004:  ldnull
  IL_0005:  ceq        // compare x and null
  IL_0007:  stloc.1    // and store the result in a
  IL_0008:  ldloc.1
  IL_0009:  ldc.i4.0 
  IL_000a:  ceq
  IL_000c:  stloc.2    // compare a and false
  IL_000d:  ldloc.2
  IL_000e:  brtrue.s   IL_0018   // if true (that is, a is false), skip
  IL_0010:  nop
  IL_0011:  call       void [mscorlib]System.Console::WriteLine()
  IL_0016:  nop
  IL_0017:  nop
  IL_0018:  ldloc.0
  IL_0019:  ldnull  
  IL_001a:  ceq        // compare x and null
  IL_001c:  ldc.i4.0
  IL_001d:  ceq        // and compare with false
  IL_001f:  stloc.2
  IL_0020:  ldloc.2
  IL_0021:  brtrue.s   IL_002b  // if true (that is, x == null), skip
  IL_0023:  nop
  IL_0024:  call       void [mscorlib]System.Console::WriteLine()
  IL_0029:  nop
  IL_002a:  nop
  IL_002b:  br.s       IL_002d
  IL_002d:  ret
} // end of method Program::Main

总的来说,有很多ldloc和stloc可以读写数据到内存;它们非常有用,可以帮助调试器。但是你可以看到有一个隐藏的局部变量,它具有与a完全相同的功能:所以如果你不使用临时变量,编译器就会为你使用它。另请注意使用通用null

现在这里是启用了优化的 Release IL:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       24 (0x18)
  .maxstack  2
  .locals init ([0] string x,
           [1] bool a)
  IL_0000:  ldnull
  IL_0001:  stloc.0     // set x to null
  IL_0002:  ldloc.0
  IL_0003:  ldnull
  IL_0004:  ceq
  IL_0006:  stloc.1     // bool a = x == null
  IL_0007:  ldloc.1
  IL_0008:  brfalse.s  IL_000f  // if false skip
  IL_000a:  call       void [mscorlib]System.Console::WriteLine()
  IL_000f:  ldloc.0
  IL_0010:  brtrue.s   IL_0017  // if true (so x != null) skip
  IL_0012:  call       void [mscorlib]System.Console::WriteLine()
  IL_0017:  ret
} // end of method Program::Main

在优化版本中,编译器不执行显式比较,也不使用临时变量。尽管如此,在未经优化的版本中,它会存储并在此之后加载它以检查条件;这是因为stloc弹出一个堆栈,所以它必须再次推送它。

现在,让我们比较一下JITter生成的代码(我设置x = Console.Readline()以防止整个代码被优化掉)。这适用于调试配置(如Visual Studio中所示):

            string x = null;
00000043  xor         edx,edx 
00000045  mov         dword ptr [ebp-40h],edx 
            bool a = x == null;
00000048  cmp         dword ptr [ebp-40h],0 
0000004c  sete        al 
0000004f  movzx       eax,al 
00000052  mov         dword ptr [ebp-44h],eax 

            if ( a == true ) { Console.WriteLine( ); }
00000055  cmp         dword ptr [ebp-44h],0 
00000059  sete        al 
0000005c  movzx       eax,al 
0000005f  mov         dword ptr [ebp-48h],eax 
00000062  cmp         dword ptr [ebp-48h],0 
00000066  jne         00000070 
00000068  nop 
00000069  call        6027B57C 
0000006e  nop 
0000006f  nop 
            if ( x == null ) { Console.WriteLine( ); }
00000054  cmp         dword ptr [ebp-0Ch],0 
00000058  jne         00000065 
0000005a  mov         ecx,dword ptr ds:[0350208Ch] 
00000060  call        602DD5E0 
            return;
00000065  nop 
00000066  mov         esp,ebp 
00000068  pop         ebp 
00000069  ret 

如您所见,此代码紧跟相应的未优化IL,并在检查a的条件时使用临时变量。另一方面,由于null在我的机器上实现为0,因此比较xnull会更快。

这是通过OllyDbg看到的发布代码:

                string x = Console.ReadLine( );
002F0075    E8 EA808A60     CALL mscorlib_ni.60B98164
002F007A    8BC8            MOV ECX, EAX
002F007C    8B01            MOV EAX, DWORD PTR DS:[ECX]
002F007E    8B40 2C         MOV EAX, DWORD PTR DS:[EAX+2C]
002F0081    FF50 1C         CALL DWORD PTR DS:[EAX+1C]

                bool a = x == null;
002F0084    8BF0            MOV ESI, EAX
002F0086    85F6            TEST ESI, ESI
002F0088    0F94C0          SETE AL
002F008B    0FB6C0          MOVZX EAX, AL
002F008E    8BF8            MOV EDI, EAX

                Systed.Diagnostics.Debugger.Break( );
002F0090    E8 E37C8E60     CALL mscorlib_ni.60BD7D78

                if ( a == true ) { Console.ReadLine( ); }
002F0095    85FF            TEST EDI, EDI
002F0097    74 0E           JE SHORT 002F00A7
002F0099    E8 A6F92D60     CALL mscorlib_ni.605CFA44
002F009E    8BC8            MOV ECX, EAX
002F00A0    8B01            MOV EAX, DWORD PTR DS:[ECX]
002F00A2    8B40 38         MOV EAX, DWORD PTR DS:[EAX+38]
002F00A5    FF10            CALL DWORD PTR DS:[EAX]

                if ( x == null ) { Console.ReadLine( ); }
002F00A7    85F6            TEST ESI, ESI
002F00A9    75 0E           JNE SHORT 002F00B9
002F00AB    E8 94F92D60     CALL mscorlib_ni.605CFA44
002F00B0    8BC8            MOV ECX, EAX
002F00B2    8B01            MOV EAX, DWORD PTR DS:[ECX]
002F00B4    8B40 38         MOV EAX, DWORD PTR DS:[EAX+38]
002F00B7    FF10            CALL DWORD PTR DS:[EAX]

                return;
002F00B9    5E              POP ESI
002F00BA    5F              POP EDI
002F00BB    5D              POP EBP
002F00BC    C3              RETN

在此代码中,a保存在edi中,x保存在esi中,并且有一些调用mscorlib来检索指向ReadLine和WriteLine的指针。话虽如此,这两种方法实际上存在差异;在将xnulltest esi, esi)进行比较后,结果将从零标记移至al(sete al),然后扩展为整个eax({ {1}})。

所以,即使在如此简单的情况下,JITter也没有做好工作;因此,如果没有临时变量,您可以获得次要性能增益。

答案 1 :(得分:2)

因此,通过现代编译和硬件优化,我怀疑是否会有足够的性能差异来制造足够的差异(如果有的话)。

像C#这样的高级语言的一部分是将这些微不足道的优化细节从app devs手中拿走,并将它们交给编译器开发人员,他们会更好地完成它并释放app devs以使他们的决策脱离可读性/ keepablity并将更多时间花在高级算法效率上而不是低级别的东西上。如果您遇到性能问题,这可能是您最不担心的问题。

底线我建议使用您认为使代码最具可读性的任何内容。

答案 2 :(得分:0)

不要关心微观优化。无论哪个“最快”,无疑对您的网络应用程序没有任何影响。使用最适合您和其他程序员阅读的内容。

除非您已经测量并发现代码存在问题,并且您确切知道代码中的确切位置,否则您会浪费时间。

程序员时间比计算机时间贵很多个数量级。