【问题标题】:C# Compiler Optimization - Catch only containing throwC# 编译器优化 - Catch 只包含 throw
【发布时间】:2014-10-14 16:16:39
【问题描述】:

我希望MyMethod 的以下两个实现的行为完全相同相同。他们有吗?如果不是,这可能已经是我的错误假设了:

第一:

public int MyMethod(int x)
{
    try
    {
        return x + 8;
    }
    catch
    {
        throw;
    }
}

第二:

public int MyMethod(int x)
{
    return x + 8;
}

因此,我假设编译器会对此进行优化,即删除第一个实现的不必要的 try/catch 块(在发布模式下)。 事实证明,它没有 - 这是为两个代码示例生成的 IL:

第一:

.method public hidebysig instance int32  MyMethod(int32 x) cil managed
{
  // Code size       11 (0xb)
  .maxstack  2
  .locals init ([0] int32 CS$1$0000)
  .try
  {
    IL_0000:  ldarg.1
    IL_0001:  ldc.i4.8
    IL_0002:  add
    IL_0003:  stloc.0
    IL_0004:  leave.s    IL_0009
  }  // end .try
  catch [mscorlib]System.Object 
  {
    IL_0006:  pop
    IL_0007:  rethrow
  }  // end handler
  IL_0009:  ldloc.0
  IL_000a:  ret
} // end of method MyClass::MyMethod

第二:

.method public hidebysig instance int32  MyMethod(int32 x) cil managed
{
  // Code size       4 (0x4)
  .maxstack  8
  IL_0000:  ldarg.1
  IL_0001:  ldc.i4.8
  IL_0002:  add
  IL_0003:  ret
} // end of method MyClass::MyMethod

有人可以解释一下吗?两种实现的行为是否存在相关差异(副作用?)?编译器可以优化代码,但不能吗?谢谢!

【问题讨论】:

  • 您认为它没有任何副作用的假设是错误的。它确实,相当大的一个,该方法不会被内联。很好在this question 中展示。对于不做任何事情的 for() 循环也是如此。不会被删除,因为它确实有副作用,需要时间。编译器和抖动只优化不明显的情况。
  • x + 8 在特定条件下可能会失败。 OP 练习:弄清楚这是如何发生的 :)

标签: c# try-catch compiler-optimization


【解决方案1】:

大多数优化由 JITter 完成,只有一些优化由生成 IL 的编译器完成。所以我不知道在程序运行期间实际编译成机器代码的是什么(虽然你可以调试)。
您可以在帖子中找到信息:http://blogs.msdn.com/b/ericlippert/archive/2009/06/11/what-does-the-optimize-switch-do.aspx 由 c# 编译器团队之一编写。
上面写着:
“/optimize 标志不会大量改变我们的发射和生成逻辑。我们尝试始终生成简单、可验证的代码,然后在生成真实机器代码时依靠抖动进行繁重的优化”

您还可以在链接中找到有关某些实际为生成 IL 代码而优化的案例的信息。

更新

对以下答案的含义进行了性能测试:
what will empty catch and throw block do?
它确认它没有被优化掉。

关于是否可以删除代码的问题,在以下位置进行了讨论: Can I remove empty catch with throw?

【讨论】:

  • 我不能同意你的观点,如果 ReSharper 检测到这种冗余,编译器也应该这样做。我无法对其进行测试,但是当您捕获非 cls-compilant 异常并重新抛出它时,它可能会在 Throw 后被打包到 RunTimeWrappedException 中。
  • 我并不是说它不能被检测到,但编译器团队的决定可能不是优化它,而是把这个优化留给 JITter。如果它被优化了...
  • 还应该记住,即使是发布版本也可以调试(有点难度),并且使用问题中显示的习语作为放置断点以检查例外。它不像设置被称为“防止调试”,所以这可能是不优化它的原因。
  • 但这将成为不内联方法或更多优化的论据。我希望对于发布版本,调试参数不应成为不优化的理由。
  • @Uwe - 它确实可以作为附加调试器时不执行这些优化的参数 - 这是可能的,因为内联是 JIT 的运行时决定,而不是 C# 编译时间决定。当实际上附加了调试器时,JIT 会做出不同的决定。
【解决方案2】:

编译器不能假定在这种情况下不需要 try/catch 块。

如果里面有任何代码,它有可能会引发异常。 例如:

  • “+”运算符可以在其中一个对象中被覆盖并抛出一个 异常...
  • 将从“try”块返回的对象的构造函数可能会引发异常...

即使您没有“处理”在“catch”语句中捕获的异常对象,许多事情也会在后台发生。例如 Exception 对象的构造函数被启动(编译器无法确定是否实现了重要的东西)。

正如您所见,从编译器的角度来看,即使是单个(几乎是空的)try/catch 块也不是那么简单。

【讨论】:

    猜你喜欢
    • 2014-04-18
    • 2014-02-21
    • 1970-01-01
    • 1970-01-01
    • 2012-02-09
    • 1970-01-01
    • 2010-12-13
    • 2012-04-11
    相关资源
    最近更新 更多