【问题标题】:Performance Considerations for throwing Exceptions抛出异常的性能注意事项
【发布时间】:2013-01-09 14:01:43
【问题描述】:

我多次遇到以下类型的代码,我想知道这是否是一种好的做法(从性能的角度来看):

try
{
    ... // some code
}
catch (Exception ex)
{
    ... // Do something
    throw new CustomException(ex);
}

基本上,编码人员所做的是他们将异常包含在自定义异常中并再次抛出。

这与以下两个在性能上有何不同:

try
{
    ... // some code
}
catch (Exception ex)
{
    .. // Do something
    throw ex;
}

try
{
    ... // some code
}
catch (Exception ex)
{
    .. // Do something
    throw;
}

抛开任何功能性或编码最佳实践论点,这 3 种方法之间是否存在性能差异?

【问题讨论】:

    标签: .net exception optimization performance


    【解决方案1】:

    @布拉德·塔特罗

    异常在第一种情况下没有丢失,它被传递给构造函数。我会同意你的其余部分,第二种方法是一个非常糟糕的主意,因为堆栈跟踪的丢失。当我使用 .NET 时,我遇到了很多其他程序员这样做的情况,当我需要查看异常的真正原因时,我感到非常沮丧,却发现它从一个巨大的 try 块中被重新抛出,其中我现在不知道问题出在哪里。

    我也赞同 Brad 的评论,即您不必担心表现。这种微优化是一个可怕的想法。除非您谈论的是在长时间运行的 for 循环的每次迭代中抛出异常,否则您很可能不会因异常使用而遇到性能问题。

    当您有指标表明您需要优化性能时,请始终优化性能,然后找到被证明是罪魁祸首的地方。

    拥有易于调试功能的可读代码(IE 不隐藏堆栈跟踪)比让某些东西运行得快一纳秒要好得多。

    关于将异常包装到自定义异常中的最后一点...这是一个非常有用的构造,尤其是在处理 UI 时。您可以将每个已知且合理的异常情况包装到某个基本自定义异常(或从所述基本异常扩展的异常)中,然后 UI 可以捕获此基本异常。当被捕获时,异常需要提供向用户显示信息的方法,比如 ReadableMessage 属性或类似的东西。因此,任何时候 UI 遗漏异常,都是因为您需要修复一个错误,而任何时候它捕获到异常,这都是已知的错误情况,可以并且应该由 UI 正确处理。

    【讨论】:

      【解决方案2】:

      显然,您会因创建新对象(新异常)而受到惩罚,因此,正如您对附加到程序的每一行代码所做的那样,您必须确定更好的异常分类是否会支付额外费用工作。

      作为做出该决定的一条建议,如果您的新对象没有携带有关异常的额外信息,那么您可能会忘记构建新的异常。

      但是,在其他情况下,具有异常层次结构对于您的类的用户来说非常方便。假设您正在实现 Facade 模式,到目前为止考虑的场景都不是很好:

      1. 将每个异常都作为 Exception 对象引发是不好的,因为您正在丢失(可能)有价值的信息
      2. 既不举起你捕捉到的每一种对象也不好,因为这样做你将无法创建外观

      在这个假设的情况下,最好的做法是创建异常类的层次结构,将用户从系统的内部复杂性中抽象出来,让他们了解所产生的异常类型。

      附注:

      我个人不喜欢使用异常(从 Exception 类派生的类的层次结构)来实现逻辑。就像案例一样:

      try {
              // something that will raise an exception almost half the time
      } catch( InsufficientFunds e) {
              // Inform the customer is broke
      } catch( UnknownAccount e ) {
              // Ask for a new account number
      }
      

      【讨论】:

        【解决方案3】:

        和大卫一样,我认为第二个和第三个表现更好。但是,这三者中的任何一个会表现得糟糕到需要花时间担心它吗?我认为还有比性能更大的问题要担心。

        FxCop 总是推荐第三种方法而不是第二种方法,这样原始堆栈跟踪就不会丢失。

        编辑:删除了完全错误的内容,Mike 很乐意指出。

        【讨论】:

          【解决方案4】:

          不要这样做:

          try
          {
              // some code
          }
          catch (Exception ex) { throw ex; }
          

          因为这会丢失堆栈跟踪。

          改为:

          try
          {
              // some code
          }
          catch (Exception ex) { throw; }
          

          只要抛出就可以了,如果你希望它成为新的自定义异常的内部异常,你只需要传递异常变量。

          【讨论】:

          • 小添加 - 删除 ex 否则您会创建未使用的变量警告。 try { // 一些代码 } catch (Exception) { throw; }
          【解决方案5】:

          正如其他人所说,最好的性能来自底部,因为您只是重新抛出现有对象。中间的最不正确,因为它丢失了堆栈。

          如果我想解耦代码中的某些依赖项,我个人会使用自定义异常。例如,我有一个从 XML 文件加载数据的方法。这可能会以多种不同的方式出错。

          它可能无法从磁盘读取(FileIOException),用户可能会尝试从不允许的地方访问它(SecurityException),文件可能已损坏(XmlParseException),数据可能格式错误(反序列化异常)。

          在这种情况下,调用类更容易理解这一切,所有这些异常都会重新抛出一个自定义异常 (FileOperationException),这意味着调用者不需要引用 System.IO 或 System.Xml,但仍然可以通过枚举和任何重要信息访问发生的错误。

          如上所述,不要尝试对这样的事情进行微优化,抛出异常的行为是这里发生的最慢的事情。最好的改进是尽量避免异常。

          public bool Load(string filepath)
          {
            if (File.Exists(filepath)) //Avoid throwing by checking state
            {
              //Wrap anyways in case something changes between check and operation
              try { .... }
              catch (IOException ioFault) { .... }
              catch (OtherException otherFault) { .... }
              return true; //Inform caller of success
            }
            else { return false; } //Inform caller of failure due to state
          }
          

          【讨论】:

            【解决方案6】:

            第一个示例中的 throw 具有创建新 CustomException 对象的开销。

            第二个示例中的重新抛出将抛出 Exception 类型的异常。

            第三个示例中的重新引发将引发与“某些代码”引发的类型相同的异常。

            所以第二个和第三个例子使用的资源更少。

            【讨论】:

              【解决方案7】:

              等等....如果抛出异常,我们为什么要关心性能?除非我们将异常用作正常应用程序流程的一部分(这是 WAYYYY 违反最佳做法)。

              我只看到了与成功有关的性能要求,但从未看到与失败有关的性能要求。

              【讨论】:

                【解决方案8】:

                从纯粹的性能角度来看,我猜第三种情况的性能最好。另外两个需要提取堆栈跟踪并构造新对象,这两者都可能相当耗时。

                话虽如此,这三个代码块具有非常不同的(外部)行为,因此比较它们就像询问 QuickSort 是否比向红黑树中添加项目更有效。这并不像选择正确的事情那么重要。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2011-07-09
                  • 2017-01-24
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2012-12-16
                  • 2012-02-03
                  相关资源
                  最近更新 更多