例外有多贵

Kai*_*ann 20 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();
}
Run Code Online (Sandbox Code Playgroud)

这与此相比如何表现

private void doSomething()
{
    doSomethingElse();
    goOn();
}
private void doSomethingElse()
{
   if(isSoAndSo())
   {
      log("A Message");
      return;
   }
   goOnAgain();
}
Run Code Online (Sandbox Code Playgroud)

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

Tof*_*eer 11

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

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

  • 项目39仅在特殊情况下使用例外.
  • 第40项对可恢复条件使用例外

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


Ron*_*Ron 11

抛出异常的最慢部分是填充堆栈跟踪.

如果您预先创建例外并重新使用它,JIT可以将其优化为" 机器级别转到".

所有这一切,除非你的问题中的代码是一个非常紧密的循环,差异将是微不足道的.


Esk*_*ola 8

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

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

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

下面通过在接受的答案中向微基准添加一个简单的方法(虽然有缺陷),演示了没有堆栈跟踪的异常:

public class DidNotWorkException extends Exception {
  public Throwable fillInStackTrace() {
      return this;
  }
}
Run Code Online (Sandbox Code Playgroud)

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

Exception:99ms
Boolean:12ms

Exception:92ms
Boolean:11ms
Run Code Online (Sandbox Code Playgroud)

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


Hen*_*y B 6

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

我得到了以下结果:

Exception:20891ms
Boolean:62ms
Run Code Online (Sandbox Code Playgroud)

从这段代码:

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 {}
}
Run Code Online (Sandbox Code Playgroud)

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

我的规格是:

  • 编译并在1.5.0_16上运行
  • Sun JVM
  • WinXP SP3
  • 英特尔迅驰双核T7200(2.00Ghz,977Mhz)
  • 2.00 GB Ram

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


Tho*_*sen 5

这本质上是 JVM 特定的,因此您不应该盲目相信给出的任何建议,而应该根据您的情况进行实际衡量。创建一个“抛出一百万个异常并打印出 System.currentTimeMillis 的差异”来获得一个粗略的想法应该不难。

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

(每当你以复杂的方式做某事时,你就会导致读者做不必要的工作,以便理解你为什么这样做而不是通常的方式——在我看来,这项工作必须由作者仔细解释原因来证明是合理的这样做是有原因的)。

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