Java中的异常速度

时间:2011-01-29 16:48:48

标签: java exception

对正常的代码流使用异常是很糟糕的 - 它很慢,代码质量很差,而且从第1天开始我们都已经把这个鼓起来了。至少我有!这也是有道理的,异常会在每次调用时生成堆栈跟踪,堆栈跟踪需要很长时间才能生成,因此抛出和捕获的异常比等效的if语句慢得多。

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

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

......但只有一个ms。

这一切都在服务器VM上运行,我知道大部分时间都会被print()语句占用,有些运行显示是否比try / catch快一点。但无论如何,他们肯定不应该接近?如何生成堆栈跟踪比单个if语句更快?

编辑:澄清一下,这个问题纯粹是学术问题 - 我完全清楚地知道正常代码流的异常是坏的,永远不会这样做!

7 个答案:

答案 0 :(得分:4)

最近在Java专家新闻中讨论了这个问题:http://www.javaspecialists.eu/archive/Issue187.html

简短回答:它不会每次都生成堆栈跟踪,而是缓存异常的实例。

答案 1 :(得分:2)

因为异常抛出点和捕获点在同一范围内,并且JIT可以证明您从未真正查看堆栈跟踪,所以可以将异常优化到单个分支中。您可以通过显式抛出自定义类型的异常来避免这种情况,理想情况是跨函数调用边界。为了控制分配成本,在非抛出的情况下,一定要构造一个所述异常对象的实例(理想情况下返回它,以确保转义分析不能将其转换为堆栈分配)。

那就是说,JIT可以做各种奇怪的事情。最好尝试尽可能与Real Code相似的测试用例,而不是像这样的假设情况,因此您可以在JIT可以应用于您的实际代码的任何优化之后看到基准测试结果。

答案 2 :(得分:1)

我相信早期版本的Java中的堆栈跟踪速度非常慢。事实上,我编写了一个循环英国网格地图1000 x 800的程序,并使用if语句,程序在10分钟内运行,在异常和捕获中运行,它在2小时内运行。这是10年前的事。

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

我也有,“例外是针对特殊情况”从一开始就鼓吹我,当我看到播放框架使用异常作为全局goto语句时,为了执行控制流程,我最初感到震惊。但是,在实际工作场景中运行Play非常快。这是对我的另一种误解完全被吹走了,并且开始思考许多使用异常作为设计模式的新方法。

答案 3 :(得分:1)

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

您可能希望将堆栈跟踪打印到ByteArrayOutputStream,以查看堆栈跟踪实际上使用的位置。

答案 4 :(得分:1)

值得注意的是,虽然许多人考虑将流量控制的例外用作“不良形式”,但它并不一定慢。例如,您可以选择

  • 测试每次迭代是否存在值
  • 假设该值存在并且在它没有
  • 时抛出异常

对于值较少存在的情况,test-each-iteration版本可能会更快。对于价值更频繁存在的情况,捕获失败版本可能会更快。不可否认,在两者之间做出选择假设您对数据有很好的理解,并且希望可以运行一个分析器来查看在两者之间切换的影响。

所有这一切,如果你真的不知道抛出的异常会更快,你可能会更好地使用每次迭代测试版本,因为它(对于很多人来说,它更明显地做了什么。

答案 5 :(得分:0)

少数事情

  • 基准测试存在缺陷。
  • catch / throw java异常快,非常快
  • 创建和收集堆栈跟踪的速度较慢(但热点会对堆栈进行注释)

话虽如此:例外情况并不缓慢。打印堆栈跟踪是另一个问题。

额外奖励:空检查很快,不使用异常时空陷阱也是如此

答案 6 :(得分:0)

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