【问题标题】:Is there a performance difference between returning inside if block, or writing if/else block?返回内部 if 块或编写 if/else 块之间是否存在性能差异?
【发布时间】:2015-01-17 09:33:56
【问题描述】:

我正在通过我的项目重构一些代码,并意识到我写了这样的东西:

if(errorCode > 0)
{
   DisplayError(errorCode);
   return;
}

// Continue to do stuff otherwise.

您可能已经猜到,该函数的返回类型为 void。当我开始查看这个时,我思考将它放在 if/else 块中是否有任何真正的区别:

if(errorCode > 0)
{
   DisplayError(errorCode);
}
else
{
   // Do other stuff
}

else 块将一直持续到函数结束,因此控制流程基本相同。这里有性能差异,还是应该使用的约定,或者这两者真的完全一样?

【问题讨论】:

  • 还有一点需要注意,return 将执行,无论if 语句的结果如何。它不是if 块的一部分。
  • 尝试这种微优化几乎总是被误导——更喜欢可读的代码而不是假设你比编译器更聪明。如果您遇到必须压缩性能、衡量和基准测试的情况,那就没有灵丹妙药了。
  • 提前返回通常更多的是风格上的改进,而不是与性能相关的,因为它减少了嵌套。见herehere
  • 鉴于您的第一个 sn-p 包含似乎是逻辑错误的内容,我想说很明显第二个更具可读性。
  • @Habib 你是对的。那是我的错字,不是我项目中的错误。我这里没有逻辑问题,只是性能和/或约定问题。

标签: c# performance if-statement return


【解决方案1】:

题外话,但评论太大了,您还可以使用出色的LinqPad 进行 IL 反汇编。

有趣的是,仅在调试模式下反汇编 IL,Guffa 的 Test2 IL 中有几个额外的nop's,根据 Guffa 的回答,它们在 x86 / x64 JIT 中编译出来:

调试 IL:

Test1:
IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  ldc.i4.0    
IL_0003:  cgt         
IL_0005:  ldc.i4.0    
IL_0006:  ceq         
IL_0008:  stloc.0     // CS$4$0000
IL_0009:  ldloc.0     // CS$4$0000
IL_000A:  brtrue.s    IL_0016
IL_000C:  nop         
IL_000D:  ldarg.0     
IL_000E:  call        System.Console.WriteLine
IL_0013:  nop         
IL_0014:  br.s        IL_0021
IL_0016:  ldstr       "ok"
IL_001B:  call        System.Console.WriteLine
IL_0020:  nop         
IL_0021:  ret         

Test2:
IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  ldc.i4.0    
IL_0003:  cgt         
IL_0005:  ldc.i4.0    
IL_0006:  ceq         
IL_0008:  stloc.0     // CS$4$0000
IL_0009:  ldloc.0     // CS$4$0000
IL_000A:  brtrue.s    IL_0017
IL_000C:  nop         
IL_000D:  ldarg.0     
IL_000E:  call        System.Console.WriteLine
IL_0013:  nop         
IL_0014:  nop     <-- Extra    
IL_0015:  br.s        IL_0024
IL_0017:  nop     <-- Extra
IL_0018:  ldstr       "ok"
IL_001D:  call        System.Console.WriteLine
IL_0022:  nop         
IL_0023:  nop     <-- Extra    
IL_0024:  ret         

这是有趣的 IMO,因为从内存中,处理器 nop's 如果它们实际被执行是浪费的,但如果它们填充以允许跳转到达随后的对齐边界是有益的。这就引出了一个问题,即为什么 IL 对nops 有意见,因为填充优化应该只是 JIT 编译器关注的问题。答案似乎无关,即大括号,可能是debugging reasons

对于发布模式,Guffa 是正确的 - 在发布模式下编译 IL 会从 IL 中删除所有 nop - IL nop 纯粹是为了允许在大括号上进行调试中断/单步执行。由 Jitter 重新引入 x86 nop 以实现均匀的指令对齐,两种方法完全相同。

发布 IL(Test1 和 Test2):

IL_0000:  ldarg.0     
IL_0001:  ldc.i4.0    
IL_0002:  ble.s       IL_000B
IL_0004:  ldarg.0     
IL_0005:  call        System.Console.WriteLine
IL_000A:  ret         
IL_000B:  ldstr       "ok"
IL_0010:  call        System.Console.WriteLine
IL_0015:  ret  

【讨论】:

  • 你是在调试模式下编译的吗?这样做时会添加额外的 nop 指令。
  • 是的,就是这样 - 在 LinqPad 中启用 optimize+ 会降低 nops + 进行其他优化。 IL 变得相同。
【解决方案2】:

两种情况下生成的代码完全相同。

(您在第一个示例中的代码周围缺少括号,但我假设这是一个错字,您实际上是在询问使用 returnelse 之间的区别。)

如果您查看这两种方法的生成代码:

public static void Test1(int errorCode) {
  if (errorCode > 0) {
    Console.WriteLine(errorCode);
    return;
  }
  Console.WriteLine("ok");
}

public static void Test2(int errorCode) {
  if (errorCode > 0) {
    Console.WriteLine(errorCode);
  } else {
    Console.WriteLine("ok");
  }
}

看起来像这样:

            if (errorCode > 0) {
011A00DA  in          al,dx  
011A00DB  push        eax  
011A00DC  mov         dword ptr [ebp-4],ecx  
011A00DF  cmp         dword ptr ds:[10F3178h],0  
011A00E6  je          011A00ED  
011A00E8  call        7470C310  
011A00ED  cmp         dword ptr [ebp-4],0  
011A00F1  jle         011A0100  
                Console.WriteLine(errorCode);
011A00F3  mov         ecx,dword ptr [ebp-4]  
011A00F6  call        73C5A920  
                return;
011A00FB  nop  
011A00FC  mov         esp,ebp  
011A00FE  pop         ebp  
011A00FF  ret  
            }
            Console.WriteLine("ok");
011A0100  mov         ecx,dword ptr ds:[3E92190h]  
011A0106  call        7359023C  
        }
011A010B  nop  
011A010C  mov         esp,ebp  
011A010E  pop         ebp  
011A010F  ret

和:

            if (errorCode > 0) {
011A0122  in          al,dx  
011A0123  push        eax  
011A0124  mov         dword ptr [ebp-4],ecx  
011A0127  cmp         dword ptr ds:[10F3178h],0  
011A012E  je          011A0135  
011A0130  call        7470C310  
011A0135  cmp         dword ptr [ebp-4],0  
011A0139  jle         011A0148  
                Console.WriteLine(errorCode);
011A013B  mov         ecx,dword ptr [ebp-4]  
011A013E  call        73C5A920  
011A0143  nop  
011A0144  mov         esp,ebp  
011A0146  pop         ebp  
011A0147  ret  
            } else {
                Console.WriteLine("ok");
011A0148  mov         ecx,dword ptr ds:[3E92190h]  
011A014E  call        7359023C  
            }
        }
011A0153  nop  
011A0154  mov         esp,ebp  
011A0156  pop         ebp  
011A0157  ret

生成的代码完全相同,直到最后一条指令。

【讨论】:

  • 哇,这真的很有帮助,同时也很有趣。介意我问你如何生成这样的代码吗?我没有任何经验/接触它。
  • @McAdam331:在 Visual Studio 中,您可以在要检查和调试的代码上放置断点。当断点命中时,打开反汇编窗口(ctrl+alt+D)。请注意,在调试和发布模式下编译的代码是不同的;在调试模式下,可能会添加额外的nop 指令用于调试目的并且代码未优化,在发布模式下,代码可以通过优化重新排列,因此它可能与源代码不完全相同。
猜你喜欢
  • 1970-01-01
  • 2013-05-01
  • 2021-02-23
  • 2019-09-21
  • 1970-01-01
  • 2015-09-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多