布尔检查比空检查更快吗? [英] Is bool check faster than null check?

查看:58
本文介绍了布尔检查比空检查更快吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

例如我是否需要提取

bool xIsNull = x == null

从我检查 x == null 的循环中?

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

As I know if (a == true) and if (x == null) both use the same IL instruction. But pointers consist of 32 or 64 bits. Does CLR should check every bit to compare with null?

更新
快速测试表明没有什么区别,但我仍然希望有人对此进行解释.

UPDATE
Quick test shows that there is no difference but I still would like somebody to explain this.

UPDATE2
我使用IL发光,所以我不能指望编译器可以优化我的代码.仅通过JIT.

UPDATE2
I use IL emitting so I can't expect compiler to optimize my code. Only by JIT.

推荐答案

记住过早的优化是万恶之源",而优化的第一条规则是不要"(第二条,仅针对专业人士,是不要做".这就是发生的情况.

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
如果您不希望陷入某些汇编代码中,我不会怪您;)结果表明,使用临时变量不会得到优化,并会生成更多指令.总结一下,但是,除非您要编写非常时间紧迫的任务,否则不会有任何区别.

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.

考虑以下代码:

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

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

这是在 Debug 模式下生成的IL(我添加了一些评论):

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

总的来说,有很多ldloc和stloc可以将数据读写到内存中.他们非常需要帮助调试器.但是您会看到有一个隐藏的局部变量,其功能与a:完全相同:因此,如果您不使用临时变量,则编译器将为您使用它.还要注意使用通用的 null .

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.

现在是启用了优化功能的 Release IL:

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

在优化版本中,编译器不执行显式比较,也不使用temp变量.仍然,像未优化的版本一样,它存储a并在此之后立即加载以检查条件;这是因为 stloc 从堆栈中弹出一个,因此必须再次将其推入.

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.

现在,让我们比较一下JITter生成的代码(我将x设置为Console.Readline()来防止整个代码被优化).这是用于调试配置的(在Visual Studio中可以看到):

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 

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

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.

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

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

在此代码中, a 被保存在 edi 中,而 x 被保存在 esi 中调用mscorlib以检索指向ReadLine和WriteLine的指针.话虽如此,两种方法实际上是有区别的.在将 x null ( test esi,esi )进行比较之后,结果从零标志移至al( sete al ),然后扩展到整个 eax ( movzx eax,al ).

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).

因此,即使在这样简单的情况下,JITter也无法做好工作;因此,您可以在没有临时变量的情况下获得次要的性能提升.

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.

这篇关于布尔检查比空检查更快吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆