Is bool check faster than null check?

前端 未结 3 629
暗喜
暗喜 2021-01-18 22:23

E.g. do I need to extract

bool xIsNull = x == null

from the loop where I check x == null?

As I know if (a == tr

相关标签:
3条回答
  • 2021-01-18 22:49

    So with modern compilation and hardware optimizations I doubt there would be enough performance difference to make enough difference to worry about (if any).

    Part of the point higher level languages like C# is to take these insignificant optimization details out of the hands of app devs and leave them to compiler devs who will do it much better and free up app devs to make their decisions off of readability/maintainablity and spend more time on high level algorithmic efficiency rather than the low level stuff. If you are having performance issues this is probably the least of your worries.

    Bottom line I would recommend using whatever you feels makes your code most readable.

    0 讨论(0)
  • 2021-01-18 23:03

    Do not concern yourself with microoptimizations. Whichever one is "fastest" will undoubtedly have zero effect on your web app. Use whichever is clearest for you and other programmers to read.

    Unless you have measured and found that there is a problem with your code, and you know exactly where in the code is being slow, you are wasting your time.

    Programmer time is many orders of magnitude more expensive than computer time.

    0 讨论(0)
  • 2021-01-18 23:15

    Remembering that "Premature Optimization is the Root of all Evil" and that the first rule of optimization is "Don't" (the second, only for pros, is "Don't Do it Yet"), here's what happens.

    TL;DR
    If you don't feel like diving into some assembly code I won't blame you ;) Results show that using a temporary variable does not get optimized out and generates a couple more instructions. Summing it up, though, it's not going to make any difference unless you are coding very time critical tasks.


    Consider this code:

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

    This is the generated IL in Debug mode (I added some comments):

    .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
    

    Overall, there are a lot of ldloc and stloc which read and write data to memory; they are highly reduntant to help the debugger. But you can see that there is an hidden local variable which has the exact same function as a: so if you don't use a temporary variable the compiler is going to use it for you. Also note the use of a generic null.

    Now here's the Release IL with optimizations enabled:

    .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
    

    In the optimized version the compiler does not perform explicit comparisons and does not use a temp variable. Still, like in the unoptimized version, it stores a and loads it right after that to check for the condition; this is because stloc pops a out of the stack so it has to push it again.

    Now, let's compare the code generated by the JITter (I set x = Console.Readline() to prevent the whole code being optimized out). This is for the debug configuration (as seen in 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 
    

    As you can see, this code follows closely the corresponding unoptimized IL and uses a temporary variable when checking the condition for a. On the other hand, since null is implemented as 0 on my machine, comparing x and null is way quicker.

    And here's the code for the release as seen through 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
    

    In this code, a is held in edi and x is held in esi and there are some calls to mscorlib to retrieve the pointers to ReadLine and WriteLine. That being said, there actually is a difference between the two approaches; after comparing x with null (test esi, esi) the result is moved from the zero flag to al (sete al), then extended to the whole eax (movzx eax, al).

    So, even in such a simple case, the JITter isn't doing a good work; therefore, you can expect a minor performance gain without the temporary variable.

    0 讨论(0)
提交回复
热议问题