2011-01-29 65 views
4

使用异常处理正常的代码流很糟糕 - 速度很慢,代码质量很差,从第1天起,我们都会把这种情况带入我们的头脑中。至少我有!这也是有道理的,异常每次调用时都会生成堆栈跟踪,堆栈跟踪需要很长时间才能生成,因此抛出和捕获的异常比等效的if语句慢得多。Java中异常的速度

所以我决定做一个快速的例子来证明这一点。

public static void main(String[] args) throws Exception { 
    Object[] objs = new Object[500000]; 
    for (int i = 0; i < objs.length; i++) { 
     if (Math.random() < 0.5) { 
      objs[i] = new Object(); 
     } 
    } 
    long cur = System.currentTimeMillis(); 
    for (Object o : objs) { 
     try { 
      System.out.print(o.getClass()); 
     } 
     catch (Exception e) { 
      System.out.print("null"); 
     } 
    } 
    System.err.println("Try/catch: "+(System.currentTimeMillis() - cur)); 
    cur = System.currentTimeMillis(); 
    for (Object o : objs) { 
     if(o==null) { 
      System.out.print("null"); 
     } 
     else { 
      System.out.print(o.getClass()); 
     } 
    } 
    System.err.println("If: "+(System.currentTimeMillis() - cur)); 
} 

然而,上运行的代码,我吃惊地看到以下内容:

Try/catch: 11204 
If: 11953 

我再次运行代码,此时 “如果” 是快:

Try/catch: 12188 
If: 12187 

。 ..但只有一个毫秒。

这是所有在服务器虚拟机上运行的,我知道大部分时间都会被print()语句占用,有些运行会显示速度比try/catch快。但无论如何,他们当然不应该接近?那么如何生成堆栈跟踪比单个if语句更快呢?

编辑:为了澄清,这个问题是一个纯粹的学术 - 我知道使用正常代码流的异常是很好,绝对不会这样做!

回答

4

这个最近被讨论在Java专家newletter:http://www.javaspecialists.eu/archive/Issue187.html

简短的回答:这段时间不产生堆栈跟踪每一次,但缓存异常的一个实例。

+0

谢谢,该通讯解释得相当好。我以前看过它,但没有那篇文章! – berry120 2011-01-30 00:26:06

0

几件事情

  • 基准测试是严重的缺陷。
  • 捕捉/抛出Java异常的速度很快,非常快
  • 创建和收集堆栈跟踪比较慢(但热点签注堆栈)

话虽这么说:异常是不慢。打印堆栈跟踪是另一个问题。

额外的好处:null检查的速度快,因此是无效的陷阱不使用异常

2

因为异常抛出点和支撑点是在相同的范围内时,与JIT不能证明你实际查看堆栈跟踪,可以将异常优化为单个分​​支。您可以通过显式抛出自定义类型的异常(理想情况下跨函数调用边界)来避免这种情况。为了控制分配成本,在非抛出的情况下,确保无论如何构建一个所述异常对象的实例(并理想地返回它,以确保逃逸分析不能将其转换为堆栈分配)。

这就是说,JIT可以做各种奇怪的事情。最好尝试一个与Real Code尽可能相似的测试用例,而不是像这样的仿真例子,所以在任何优化之后,您都可以看到JIT可以应用于真实代码的基准测试结果。

1

我相信早期版本的Java中堆栈跟踪过去非常缓慢。事实上,我编写了一个程序,其中包含英国网格地图1000 x 800,并使用if语句,该程序在10分钟内运行,在异常情况下运行并捕获,并在2小时内运行。这是十年前。

最近,JVM在处理try-catch方面获得了更高的效率。

我从一开始就把“例外情况用于特殊情况”卷入了我的行列,当我看到游戏框架将异常作为全局goto语句执行控制流程时,我最初感到震惊。但是,在真实的工作场景中运行Play并且速度非常快。这是我完全失去的另一种误解,并开放了我的想法,以许多使用例外作为设计模式的新方法。

1

其基本原因是堆栈跟踪数据的数量现在被推迟到实际需要时,最初在创建异常时填充它。

您可能想要将堆栈跟踪打印到ByteArrayOutputStream中,以查看堆栈跟踪实际上的位置使用了

1

值得注意的是,虽然很多人认为流量控制的异常是“不良形式”,但并不一定比较慢。例如,你可能有

  • 之间做出选择测试是否存在的值在每次迭代
  • 假设值存在,并且抛出一个异常时,它不

对于当该值存在次数不多,每个迭代版本的测试可能会更快。对于价值更经常存在的情况,失败的版本可能会更快。无可否认,在两者之间做出选择假定您对数据有很好的理解,并且希望能够运行一个分析器来查看两者之间切换的影响。所有这些都说了,如果你实际上不知道抛出的异常会更快,那么使用每次迭代测试可能会更好,因为它对许多人来说更为明显它在做。

+0

我完全同意 - 问题来自纯粹的学术观点。除非有很好的理由,否则我不会主张在正常的代码流中使用异常! – berry120 2011-01-29 17:33:16

+0

我更加注意到这样一个概念,即不应该使用异常,因为它们比较慢。即使它们确实比较慢,在某些情况下,您可能会选择1慢速和1000快速的事情,并且此时决定会变得不太清晰。 – RHSeeger 2011-01-29 17:38:30

0

但是,速度并不是在正常流程中不使用异常的唯一原因。例外情况是一种机制,表明方法的合同被破坏。如果你开始使用异常作为流量控制,那么它就会失败。通常你可以写几行代码来避免这种异常。