【问题标题】:What is happening in an "If(..||..)" and "If(...&&...)" construct internally?"If(..||..)" 和 "If(...&&...)" 构造在内部发生了什么?
【发布时间】:2015-03-19 09:50:50
【问题描述】:

我只是想知道“if OR”和“if AND”内部会发生什么。我有一种感觉,使用 &&|| 只是 syntactic sugar,并且在内部所有案例都构建为单个 if 语句。

紧缩形式||:

if(a || b || c)
{
    DoSomething();
}

潜在的内部形式:

if(a)
{
    DoSomething();
}    
else if(b)
{
    DoSomething();
}    
else if(c)
{
    DoSomething();
}

紧缩形式 &&:

if(a && b && c)
{
    DoSomething();
}

潜在的内部形式:

if(a)
{
    if(b)
    {
        if(c)
        {
            DoSomething();
        }
    }
}

这两个例子的表现有什么不同吗?

*编辑:将 else 添加到 ||案例

【问题讨论】:

  • 为什么不自己检查一下?
  • 严格来说,你对||的解释是错误的——你提供的代码会让DoSomething();被执行3次。
  • 请考虑abc 是在很长一段时间后返回bool 的昂贵方法。然后很清楚为什么应该首选短路运算符而不是连续的ifs。但即使它们不是方法,但 bool 变量 DoSomething() 会被执行三次而不是一次,如果所有这些都是真的,这是完全不同的。
  • 这个问题的一般形式(被问了很多,例如“我应该将 memcpy 用于结构吗?”)通常归结为“我可以确定该语言知道如何实现 以最好的方式拥有自己的内置功能?”。为什么你认为它不会?

标签: c# if-statement


【解决方案1】:

首先,||&&short-circuit。这意味着在:

if(a || b || c)
    DoSomething();

如果a 为真,则不会评估bc

其次,您对|| 的实现是错误的:

if(a)
    DoSomething();
if(b)
    DoSomething();
if(c)
    DoSomething();

DoSomething() 将被调用最多 3 次

应该是:

if(a)
    DoSomething();
else if(b)
    DoSomething();
else if(c)
    DoSomething();

最后,如果您希望性能在您的条件下优先选择较短的调用:

if(aShortFunctionToExecute() || aVeryVeryLongFunctionToExecute())
     DoSomething();

会比

更快
if(aVeryVeryLongFunctionToExecute() || aShortFunctionToExecute())
     DoSomething();

因为lazy-evaluation


如果你反汇编以下代码:

private static void Main()
{
    if (a() && b() && c())
    {
        Console.WriteLine("DoSomething");
    }
}
bool a(){
    return true;
}
bool b(){
    return 3 % 2 == 1;
}
bool c(){
    return (3 % 2) / 1 == 1;
}

你会得到:

    if (a() && b() && c())
00000022  call        FFFFFFFFFFEE8D90 
00000027  mov         byte ptr [rbp+20h],al 
0000002a  movzx       eax,byte ptr [rbp+20h] 
0000002e  test        eax,eax 
00000030  je          000000000000005A 
00000032  call        FFFFFFFFFFEE8D98 
00000037  mov         byte ptr [rbp+21h],al 
0000003a  movzx       eax,byte ptr [rbp+21h] 
0000003e  test        eax,eax 
00000040  je          000000000000005A 
00000042  call        FFFFFFFFFFEE8DA0 
00000047  mov         byte ptr [rbp+22h],al 
0000004a  movzx       ecx,byte ptr [rbp+22h] 
0000004e  xor         eax,eax 
00000050  test        ecx,ecx 
00000052  sete        al 
00000055  mov         dword ptr [rbp+24h],eax 
00000058  jmp         0000000000000062 
0000005a  nop 
0000005b  mov         dword ptr [rbp+24h],1 
00000062  nop 
00000063  movzx       eax,byte ptr [rbp+24h] 
00000067  mov         byte ptr [rbp+2Fh],al 
0000006a  movzx       eax,byte ptr [rbp+2Fh] 
0000006e  test        eax,eax 
00000070  jne         0000000000000087 
        {
00000072  nop 
            Console.WriteLine("DoSomething");
00000073  mov         rcx,12603398h 
0000007d  mov         rcx,qword ptr [rcx] 
00000080  call        00000000577A82A0 
00000085  nop 
        }

对于代码:

private static void Main()
{
    if (a())
        if(b())
            if(c())
                Console.WriteLine("DoSomething");
}
static bool a(){
    return true;
}
static bool b(){
    return 3 % 2 == 1;
}
static bool c(){
    return (3 % 2) / 1 == 1;
}

你会得到:

if (a())
00000022  call        FFFFFFFFFFEE8D90 
00000027  mov         byte ptr [rbp+20h],al 
0000002a  movzx       ecx,byte ptr [rbp+20h] 
0000002e  xor         eax,eax 
00000030  test        ecx,ecx 
00000032  sete        al 
00000035  mov         dword ptr [rbp+24h],eax 
00000038  movzx       eax,byte ptr [rbp+24h] 
0000003c  mov         byte ptr [rbp+3Fh],al 
0000003f  movzx       eax,byte ptr [rbp+3Fh] 
00000043  test        eax,eax 
00000045  jne         00000000000000A4 
            if(b())
00000047  call        FFFFFFFFFFEE8D98 
0000004c  mov         byte ptr [rbp+28h],al 
0000004f  movzx       ecx,byte ptr [rbp+28h] 
00000053  xor         eax,eax 
00000055  test        ecx,ecx 
00000057  sete        al 
0000005a  mov         dword ptr [rbp+2Ch],eax 
0000005d  movzx       eax,byte ptr [rbp+2Ch] 
00000061  mov         byte ptr [rbp+3Fh],al 
00000064  movzx       eax,byte ptr [rbp+3Fh] 
00000068  test        eax,eax 
0000006a  jne         00000000000000A4 
                if(c())
0000006c  call        FFFFFFFFFFEE8DA0 
00000071  mov         byte ptr [rbp+30h],al 
00000074  movzx       ecx,byte ptr [rbp+30h] 
00000078  xor         eax,eax 
0000007a  test        ecx,ecx 
0000007c  sete        al 
0000007f  mov         dword ptr [rbp+34h],eax 
00000082  movzx       eax,byte ptr [rbp+34h] 
00000086  mov         byte ptr [rbp+3Fh],al 
00000089  movzx       eax,byte ptr [rbp+3Fh] 
0000008d  test        eax,eax 
0000008f  jne         00000000000000A4 
                    Console.WriteLine("DoSomething");
00000091  mov         rcx,125D3398h 
0000009b  mov         rcx,qword ptr [rcx] 
0000009e  call        00000000577B82A0 
000000a3  nop 

更长一点:它需要 40 条指令而不是 31 条。


正如thanosqr 所指出的,性能还取决于您的条件为真的概率。以他为例:

如果a 在 99% 的情况下失败并且需要 1 秒才能运行,如果 b 99% 的时间成功并花 10 秒的时间运行,超过 100 次尝试你会更快地将b 放在首位:

if(b || a) => 10s 99% ==> 100 runs will take 99*10+11 = 1001s
if(b || a) => 11s 1%

if(a || b) => 11s 99% ==> 100 runs will take 99*11+1 = 1090s
if(a || b) => 1s 1%

另外,我建议你阅读“Why is it faster to process a sorted array than an unsorted array?”,这很有趣!

【讨论】:

  • “使用紧凑方式快 25%。”不,这不对。指令减少 25%。指令的数量并不能说明它的运行速度,它会根据许多不同的参数而有很大的不同。
  • 我会说性能还取决于短路的机会;例如:如果快速检查需要 1 秒并且在 100 次中失败 99 次,而慢速检查需要 10 秒但仅失败 1 次,(慢 || 快)需要 1001 秒,而(快 || 慢)需要 1090 秒。极端情况是检查总是错误的,即使它非常快,最好先检查慢的。
  • 请澄清“IL”的定义。
  • @Thomas 中级语言? MSIL?请看下面的答案。
【解决方案2】:

使用紧凑形式,C# 编译器发出的 IL 将不那么冗长,从而减少在运行时要处理的指令。发出的 IL 语句及其逻辑实际上是相同的,因此没有花哨的内置支持来处理这种情况或一些特殊指令(请记住,您可以将带有布尔结果的 any 表达式放入if)。

对于使用|| 运算符的紧凑形式(调试版本):

.method private hidebysig static void  One() cil managed
{
  // Code size       38 (0x26)
  .maxstack  2
  .locals init ([0] bool CS$4$0000)
  IL_0000:  nop
  IL_0001:  ldsfld     bool ConsoleApplication4.Program::a
  IL_0006:  brtrue.s   IL_0019
  IL_0008:  ldsfld     bool ConsoleApplication4.Program::b
  IL_000d:  brtrue.s   IL_0019
  IL_000f:  ldsfld     bool ConsoleApplication4.Program::c
  IL_0014:  ldc.i4.0
  IL_0015:  ceq
  IL_0017:  br.s       IL_001a
  IL_0019:  ldc.i4.0
  IL_001a:  nop
  IL_001b:  stloc.0
  IL_001c:  ldloc.0
  IL_001d:  brtrue.s   IL_0025
  IL_001f:  call       void ConsoleApplication4.Program::DoSomething()
  IL_0024:  nop
  IL_0025:  ret
} // end of method Program::One

使用您的内部表单(考虑到您使用的是else if 而不是if):

.method private hidebysig static void  Two() cil managed
{
  // Code size       60 (0x3c)
  .maxstack  2
  .locals init ([0] bool CS$4$0000)
  IL_0000:  nop
  IL_0001:  ldsfld     bool ConsoleApplication4.Program::a
  IL_0006:  ldc.i4.0
  IL_0007:  ceq
  IL_0009:  stloc.0
  IL_000a:  ldloc.0
  IL_000b:  brtrue.s   IL_0015
  IL_000d:  call       void ConsoleApplication4.Program::DoSomething()
  IL_0012:  nop
  IL_0013:  br.s       IL_003b
  IL_0015:  ldsfld     bool ConsoleApplication4.Program::b
  IL_001a:  ldc.i4.0
  IL_001b:  ceq
  IL_001d:  stloc.0
  IL_001e:  ldloc.0
  IL_001f:  brtrue.s   IL_0029
  IL_0021:  call       void ConsoleApplication4.Program::DoSomething()
  IL_0026:  nop
  IL_0027:  br.s       IL_003b
  IL_0029:  ldsfld     bool ConsoleApplication4.Program::c
  IL_002e:  ldc.i4.0
  IL_002f:  ceq
  IL_0031:  stloc.0
  IL_0032:  ldloc.0
  IL_0033:  brtrue.s   IL_003b
  IL_0035:  call       void ConsoleApplication4.Program::DoSomething()
  IL_003a:  nop
  IL_003b:  ret
} // end of method Program::Two

所以有更多的指令来处理额外的if 语句所需的所有跳转。因此,第一种形式更有效(实际上更具可读性:))。

在性能方面(每种方法测量 10 次,迭代 10.000.000 次并删除最高和最低值,发布构建):

紧凑型:平均 55 毫秒

详细形式:平均 56 毫秒

所以根本没有什么大的区别。

【讨论】:

  • @Gene,更快,不是吗? :)
【解决方案3】:

对于读C#胜过汇编的人来说,真正的内部形式更接近于:

if(a) goto yes;
if(b) goto yes;
if(c) goto yes;
goto no;
yes:  DoSomething();
goto done;
no:   /* if there were an else it would go here */;
done: ;

if(a || b || c)
  DoSomething();

if(!a) goto no;
if(!b) goto no;
if(!c) goto no;
yes:  DoSomething();
goto done;
no:   /* if there were an else it would go here */;
done: ;

if(a && b && c)
  DoSomething();

这是因为实际的指令是条件分支——在内部形式中,if 不可能与块、嵌套的 if 或除 goto 之外的任何东西相关联。

【讨论】:

    【解决方案4】:

    代码:

    if(a)
        if(b)
            if(c)
                DoSomething();
    

    是一个符合逻辑(但不是“实用”)的等价物:

    if(a && b && c)
        DoSomething();
    

    至于OR 运算符,您有点搞错了。一个符合逻辑(但同样不是“实用”)的等价物:

    if(a || b || c)
        DoSomething();
    

    应该是:

    if(a)
        DoSomething();
    else if(b)
        DoSomething();
    else if(c)
        DoSomething();
    

    通过实际差异,我理解编译器引入的任何代码差异(有关详细信息,请参阅其他答案)。

    【讨论】:

      【解决方案5】:

      ||&& 是条件运算符。他们也是操作员,就像您可能认识的其他操作员一样。 (例如+*、...)

      它们的行为类似于逻辑运算符|&。它们接收两个bool 类型变量并以这种方式返回bool 值:

      // If one of them is true, the return value is true. Otherwise, it's false.
      true  | true  == true
      true  | false == true
      false | true  == true
      false | false == false
      // If both of them are true, the return value is true. Otherwise, it's false.
      true  & true  == true
      true  & false == false
      false & true  == false
      false & false == false
      

      但是,对于条件运算符,有一点区别:short-circuit

      假设这段代码:

      bool func1() { .. }
      bool func2() { .. }
      
      bool b1 = func1() || func2();
      bool b2 = func1() && func2();
      

      如果func1() 返回true,则b1 变为true,无论func2() 返回什么。因此,我们不需要调用func2(),实际上也不需要。如果func1() 返回false,同样的事情也适用于b2。这种行为称为短路。


      现在,让我们想想你的例子。

      if (a || b || c)
          DoSomething();
      

      等于

      bool value = a || b || c;
      if (value)
          DoSomething();
      

      由于条件运算符的order of evaluation是从左到右的,所以等于

      bool value = (a || b) || c;
      if (value)
          DoSomething();
      

      【讨论】:

        【解决方案6】:

        它们的 VB 等价物可以更具描述性。 ||OrElse&& 在 VB 中是 AndAlso
        这些是条件运算符;这意味着他们制定了控制条款-在您的情况下为if-根据需要评估条件,而不是始终评估所有条件。

        例如,在if ( a || b ) 中,如果a 为真,那么b 是什么并不重要;结果为真,因此b 不会被评估,这将导致更快的执行。

        此功能也可以用作空值检查机制。如果a 为空且不为空,则if ( a != null && a.prop == somevalue ) 将阻止空引用异常,如果它不为空,则将访问其prop 属性以评估第二个条件。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-10-05
          • 1970-01-01
          相关资源
          最近更新 更多