例外有多贵

时间:2009-02-19 22:41:44

标签: java performance exception

你知道java中的异常抛出和处理有多贵吗?

我们对团队中的例外实际成本进行了多次讨论。有些人认为通过使用例外而导致的性能损失被高估了。

今天我在软件中找到了以下代码:

private void doSomething()
{
    try
    {
      doSomethingElse();
    }
    catch(DidNotWorkException e)
    {
       log("A Message");
    }
    goOn();
}
private void doSomethingElse()
{
   if(isSoAndSo())
   {
      throw new DidNotWorkException();
   }
   goOnAgain();
}

的表现相比如何?
private void doSomething()
{
    doSomethingElse();
    goOn();
}
private void doSomethingElse()
{
   if(isSoAndSo())
   {
      log("A Message");
      return;
   }
   goOnAgain();
}

我不想讨论代码美学或任何东西,它只是关于运行时行为! 你有真实的经验/测量吗?

10 个答案:

答案 0 :(得分:11)

例外不是免费的...所以它们很贵: - )

这本书Effective Java详细介绍了这一点。

  • 项目39仅在例外条件下使用例外。
  • 第40项对可恢复条件使用例外

作者发现异常导致代码调整速度慢了70倍,因为他的机器上的测试用具有特定的VM和OS组合。

答案 1 :(得分:11)

抛出异常的最慢部分是filling in the stack trace

如果您预先创建例外并重新使用它,JIT可能会将其优化为“a machine level goto。”

所有这一切都被说过,除非您的问题中的代码处于非常紧密的循环中,否则差异可以忽略不计。

答案 2 :(得分:8)

关于异常的缓慢部分是构建堆栈跟踪(在java.lang.Throwable的构造函数中),这取决于堆栈深度。投掷本身并不慢。

使用例外来表示失败。然后,性能影响可以忽略不计,堆栈跟踪有助于确定故障原因。

如果您需要控制流的异常(不推荐),并且分析显示异常是瓶颈,那么创建一个Exception子类,用一个空实现覆盖fillInStackTrace()。或者(或另外)仅实例化一个异常,将其存储在字段中并始终抛出相同的实例。

以下通过向albeit flawed中的微基准(accepted answer)添加一个简单方法来演示没有堆栈跟踪的异常:

public class DidNotWorkException extends Exception {
  public Throwable fillInStackTrace() {
      return this;
  }
}

-server模式下运行JVM(Windows 7上的版本1.6.0_24)会导致:

Exception:99ms
Boolean:12ms

Exception:92ms
Boolean:11ms

差异很小,在实践中可以忽略不计。

答案 3 :(得分:6)

我没有费心阅读Exceptions,但是用你的一些修改过的代码做了一个非常快速的测试我得出的结论是异常情况比布尔情况慢得多。

我得到了以下结果:

Exception:20891ms
Boolean:62ms

从这段代码:

public class Test {
    public static void main(String args[]) {
            Test t = new Test();
            t.testException();
            t.testBoolean();
    }
    public void testException() {
            long start = System.currentTimeMillis();
            for(long i = 0; i <= 10000000L; ++i)
                    doSomethingException();
            System.out.println("Exception:" + (System.currentTimeMillis()-start) + "ms");
    }
    public void testBoolean() {
            long start = System.currentTimeMillis();
            for(long i = 0; i <= 10000000L; ++i)
                    doSomething();
            System.out.println("Boolean:" + (System.currentTimeMillis()-start) + "ms");
    }

    private void doSomethingException() {
        try {
          doSomethingElseException();
        } catch(DidNotWorkException e) {
           //Msg
        }
    }
    private void doSomethingElseException() throws DidNotWorkException {
       if(!isSoAndSo()) {
          throw new DidNotWorkException();
       }
    }
    private void doSomething() {
        if(!doSomethingElse())
            ;//Msg
    }
    private boolean doSomethingElse() {
       if(!isSoAndSo())
          return false;
       return true;
    }
    private boolean isSoAndSo() { return false; }
    public class DidNotWorkException extends Exception {}
}

我愚蠢地没有充分阅读我的代码并且之前有一个错误(多么令人尴尬),如果有人可以三重检查这段代码我会非常喜欢它,以防万一我老去了。 / p>

我的说明是:

  • 编译并在1.5.0_16
  • 上运行
  • Sun JVM
  • WinXP SP3
  • Intel Centrino Duo T7200(2.00Ghz,977Mhz)
  • 2.00 GB Ram

在我看来,您应该注意到非异常方法不会在doSomethingElse中给出日志错误,而是返回一个布尔值,以便调用代码可以处理失败。如果有多个区域可能会失败,则可能需要在内部记录错误或抛出异常。

答案 4 :(得分:4)

这本身就是特定于JVM的,所以你不应该盲目地信任给出的任何建议,而是在你的情况下实际测量。创建一个“抛出一百万个异常并打印出System.currentTimeMillis的差异”以获得一个粗略的想法应该不难。

对于您列出的代码段,我个人会要求原作者详细记录他在此使用异常抛出的原因,因为它不是“最不出意的路径”,这对于以后维护它至关重要。

(每当你以一种错综复杂的方式做某事时,你会让读者做出不必要的工作,以便理解你为什么这样做,而不仅仅是通常的方式 - 作者必须证明这项工作是合理的。仔细解释为什么这样做是因为必须有原因)。

例外是一个非常非常有用的工具,但只应在必要时使用:)

答案 5 :(得分:3)

我没有真正的测量,但抛出异常更加昂贵。

好的,这是关于.NET框架的链接,但我认为同样适用于Java:

exceptions & performance

也就是说,当适当时,您应该毫不犹豫地使用它们。那就是:不要将它们用于流量控制,而是在出现特殊情况时使用它们;你不希望发生的事情。

答案 6 :(得分:3)

我认为如果我们坚持在需要的地方使用例外情况(特殊情况),那么这些好处远远超过您可能要支付的任何性能损失。我说可能因为成本实际上是正在运行的应用程序中抛出异常的频率的函数 在您给出的示例中,看起来失败不是意外或灾难性的,因此该方法应该返回一个bool来指示其成功状态而不是使用异常,从而使它们成为常规控制流的一部分。
在我参与的一些绩效改进工作中,例外成本相当低。您将花费更多的时间来提高常见的,重复的操作的复杂性。

答案 7 :(得分:3)

感谢您的所有回复。

我终于按照Thorbjørn的建议编写了一个小测试程序,自己测量性能。结果是:没有两个变体之间的差异(在性能方面)。

即使我没有询问代码美学或其他什么,例如异常的意图是什么等等,你们大多数人也都在谈论这个主题。但实际上事情并不总是那么清楚......在正在考虑的情况下,代码诞生了很久以前,抛出异常的情况似乎是一个例外情况。今天库的使用方式不同,不同应用程序的行为和使用情况发生了变化,测试覆盖率不是很好,但代码仍然可以完成它的工作,只是有点太慢(这就是我要求性能的原因!!)。在那种情况下,我认为,应该有一个很好的理由从A变为B,在我看来,这不是“那不是例外的原因!”。

事实证明,记录(“消息”)(与其他所有事情相比)非常昂贵,所以我想,我会摆脱它。

修改

测试代码与原始帖子中的测试代码完全相同,由testPerfomance()循环中的方法System.currentTimeMillis()调用 - 调用以获取执行时间......但是:< / p>

我现在回顾了测试代码,关闭了其他所有内容(日志语句)并循环了100多次,而且事实证明,当使用B而不是原来的A时,为一百万次调用节省了4.7秒帖子。正如Ron所说,fillStackTrace是最昂贵的部分(+1),如果你覆盖它,你可以保存几乎相同(4.5秒)(如果你不需要它,就像我一样)。总而言之,它在我的情况下仍然几乎为零,因为代码每小时被调用1000次,测量显示我可以节省4.5毫米......

所以,我上面的第一个回答部分有点误导,但我所说的关于平衡重构的成本效益仍然是正确的。

答案 8 :(得分:0)

我认为你从一个错误的角度问这个问题。例外设计用于表示特殊情况,并作为程序流机制用于表示这些情况。所以你应该问的问题是,代码的“逻辑”是否需要异常。

例外通常被设计为在其预期用途中表现良好。如果他们的使用方式使他们成为瓶颈,那么最重要的是,这可能表明他们只是被用于“错误的事情”的完全停止 - 也就是说,你所拥有的是一个程序< em>设计问题而不是性能问题。

相反,如果异常似乎是“用于正确的事情”,那么这可能意味着它也会表现良好。

答案 9 :(得分:0)

假设在尝试执行语句1和2时不会发生异常。这两个样本代码之间是否有任何性能命中?

如果不是,如果DoSomething()方法必须执行 huuuge 工作量(加载对其他方法的调用等),该怎么办?

1:

try
{
   DoSomething();
}
catch (...)
{
   ...
}

2:

DoSomething();