Java异常有多慢?

本文翻译自:How slow are Java exceptions?

Question: Is exception handling in Java actually slow? 问题:Java中的异常处理实际上是否很慢?

Conventional wisdom, as well as a lot of Google results, says that exceptional logic shouldn't be used for normal program flow in Java. 传统观念以及许多谷歌搜索结果表明,不应将特殊逻辑用于Java中的正常程序流程。 Two reasons are usually given, 通常有两个原因,

  1. it is really slow - even an order of magnitude slower than regular code (the reasons given vary), 它确实很慢 - 甚至比常规代码慢一个数量级(给出的原因各不相同),

and

  1. it is messy because people expect only errors to be handled in exceptional code. 它很混乱,因为人们只希望在特殊代码中处理错误。

This question is about #1. 这个问题是关于#1。

As an example, this page describes Java exception handling as "very slow" and relates the slowness to the creation of the exception message string - "this string is then used in creating the exception object that is thrown. This is not fast." 例如, 这个页面将Java异常处理描述为“非常慢”,并将缓慢与异常消息字符串的创建联系起来 - “然后将此字符串用于创建抛出的异常对象。这并不快。” The article Effective Exception Handling in Java says that "the reason for this is due to the object creation aspect of exception handling, which thereby makes throwing exceptions inherently slow". Java中的有效异常处理这篇文章说“其原因在于异常处理的对象创建方面,从而使异常本身变得缓慢”。 Another reason out there is that the stack trace generation is what slows it down. 另一个原因是堆栈跟踪生成减慢了它的速度。

My testing (using Java 1.6.0_07, Java HotSpot 10.0, on 32 bit Linux), indicates that exception handling is no slower than regular code. 我的测试(使用Java 1.6.0_07,Java HotSpot 10.0,在32位Linux上)表明异常处理并不比常规代码慢。 I tried running a method in a loop that executes some code. 我尝试在循环中运行一个执行一些代码的方法。 At the end of the method, I use a boolean to indicate whether to return or throw . 在方法结束时,我使用布尔值来指示是返回还是抛出 This way the actual processing is the same. 这样实际处理是一样的。 I tried running the methods in different orders and averaging my test times, thinking it may have been the JVM warming up. 我尝试以不同的顺序运行方法并平均我的测试时间,认为它可能是JVM升温。 In all my tests, the throw was at least as fast as the return, if not faster (up to 3.1% faster). 在我的所有测试中,投掷至少与返回一样快,如果不是更快(最多快3.1%)。 I am completely open to the possibility that my tests were wrong, but I haven't seen anything out there in the way of the code sample, test comparisons, or results in the last year or two that show exception handling in Java to actually be slow. 我对我的测试错误的可能性持开放态度,但我没有看到代码示例,测试比较或过去一两年中显示Java中的异常处理的结果慢。

What leads me down this path was an API I needed to use that threw exceptions as part of normal control logic. 让我沿着这条路走下去的是我需要使用的API,它将异常作为普通控制逻辑的一部分。 I wanted to correct them in their usage, but now I may not be able to. 我想在他们的使用中纠正它们,但现在我可能无法做到。 Will I instead have to praise them on their forward thinking? 相反,我是否必须赞美他们的前瞻性思维?

In the paper Efficient Java exception handling in just-in-time compilation , the authors suggest that the presence of exception handlers alone, even if no exceptions are thrown, is enough to prevent the JIT compiler from optimizing the code properly, thus slowing it down. 即时编译中的高效Java异常处理文章中 ,作者建议单独存在异常处理程序,即使没有抛出异常,也足以阻止JIT编译器正确优化代码,从而减慢它的速度。 I haven't tested this theory yet. 我还没有测试过这个理论。


#1楼

参考:https://stackoom.com/question/1Fng/Java异常有多慢


#2楼

I've extends the answers given by @Mecki and @incarnate , without stacktrace filling for Java. 我扩展了@Mecki@incarnate给出的答案,没有堆栈填充Java。

With Java 7+, we can use Throwable(String message, Throwable cause, boolean enableSuppression,boolean writableStackTrace) . 使用Java 7+,我们可以使用Throwable(String message, Throwable cause, boolean enableSuppression,boolean writableStackTrace) But for Java6, see my answer for this question 但对于Java6,请参阅我对此问题的回答

// This one will regularly throw one
public void method4(int i) throws NoStackTraceThrowable {
    value = ((value + i) / i) << 1;
    // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
    // an AND operation between two integers. The size of the number plays
    // no role. AND on 32 BIT always ANDs all 32 bits
    if ((i & 0x1) == 1) {
        throw new NoStackTraceThrowable();
    }
}

// This one will regularly throw one
public void method5(int i) throws NoStackTraceRuntimeException {
    value = ((value + i) / i) << 1;
    // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
    // an AND operation between two integers. The size of the number plays
    // no role. AND on 32 BIT always ANDs all 32 bits
    if ((i & 0x1) == 1) {
        throw new NoStackTraceRuntimeException();
    }
}

public static void main(String[] args) {
    int i;
    long l;
    Test t = new Test();

    l = System.currentTimeMillis();
    t.reset();
    for (i = 1; i < 100000000; i++) {
        try {
            t.method4(i);
        } catch (NoStackTraceThrowable e) {
            // Do nothing here, as we will get here
        }
    }
    l = System.currentTimeMillis() - l;
    System.out.println( "method4 took " + l + " ms, result was " + t.getValue() );


    l = System.currentTimeMillis();
    t.reset();
    for (i = 1; i < 100000000; i++) {
        try {
            t.method5(i);
        } catch (RuntimeException e) {
            // Do nothing here, as we will get here
        }
    }
    l = System.currentTimeMillis() - l;
    System.out.println( "method5 took " + l + " ms, result was " + t.getValue() );
}

Output with Java 1.6.0_45, on Core i7, 8GB RAM: 在Core i7,8GB RAM上使用Java 1.6.0_45输出:

method1 took 883 ms, result was 2
method2 took 882 ms, result was 2
method3 took 32270 ms, result was 2 // throws Exception
method4 took 8114 ms, result was 2 // throws NoStackTraceThrowable
method5 took 8086 ms, result was 2 // throws NoStackTraceRuntimeException

So, still methods which returns values are faster, compared to methods throwing exceptions. 因此,与抛出异常的方法相比,返回值的方法仍然更快。 IMHO, we can't design a clear API just using return types for both success & error flows. 恕我直言,我们无法为成功和错误流程使用返回类型设计一个明确的API。 Methods which throws exceptions without stacktrace are 4-5 times faster than normal Exceptions. 抛出异常而没有堆栈跟踪的方法比正常异常快4-5倍。

Edit: NoStackTraceThrowable.java Thanks @Greg 编辑:NoStackTraceThrowable.java 谢谢@Greg

public class NoStackTraceThrowable extends Throwable { 
    public NoStackTraceThrowable() { 
        super("my special throwable", null, false, false);
    }
}

#3楼

Aleksey Shipilëv did a very thorough analysis in which he benchmarks Java exceptions under various combinations of conditions: AlekseyShipilëv进行了非常彻底的分析 ,他在各种条件组合下对Java异常进行了基准测试:

  • Newly created exceptions vs pre-created exceptions 新创建的异常与预先创建的异常
  • Stack trace enabled vs disabled 启用堆栈跟踪与禁用
  • Stack trace requested vs never requested 请求的堆栈跟踪vs从未请求过
  • Caught at the top level vs rethrown at every level vs chained/wrapped at every level 处于最高级别,而不是每个级别的rethrown,而不是链接/包裹在每个级别
  • Various levels of Java call stack depth 各种级别的Java调用堆栈深度
  • No inlining optimizations vs extreme inlining vs default settings 没有内联优化与极端内联与默认设置
  • User-defined fields read vs not read 用户定义的字段读取与未读取

He also compares them to the performance of checking an error code at various levels of error frequency. 他还将它们与在各种错误频率级别检查错误代码的性能进行了比较。

The conclusions (quoted verbatim from his post) were: 结论(从他的帖子中逐字引用)是:

  1. Truly exceptional exceptions are beautifully performant. 真正特殊的例外情况非常出色。 If you use them as designed, and only communicate the truly exceptional cases among the overwhelmingly large number of non-exceptional cases handled by regular code, then using exceptions is the performance win. 如果按照设计使用它们,并且只在常规代码处理的绝大多数非例外情况中传达真正例外情况,那么使用异常就是性能获胜。

  2. The performance costs of exceptions have two major components: stack trace construction when Exception is instantiated and stack unwinding during Exception throw. 异常的性能成本有两个主要组件:实例化异常时的堆栈跟踪构造和异常抛出期间的堆栈展开

  3. Stack trace construction costs are proportional to stack depth at the moment of exception instantiation. 堆栈跟踪构建成本与异常实例化时的堆栈深度成比例 That is already bad because who on Earth knows the stack depth at which this throwing method would be called? 那已经很糟糕了,因为地球上的谁知道这个投掷方法的堆栈深度? Even if you turn off the stack trace generation and/or cache the exceptions, you can only get rid of this part of the performance cost. 即使您关闭堆栈跟踪生成和/或缓存异常,您也只能摆脱这部分性能成本。

  4. Stack unwinding costs depend on how lucky we are with bringing the exception handler closer in the compiled code. 堆栈展开成本取决于我们在编译代码中使异常处理程序更接近的程度。 Carefully structuring the code to avoid deep exception handlers lookup is probably helping us get luckier. 仔细构建代码以避免深度异常处理程序查找可能有助于我们更幸运。

  5. Should we eliminate both effects, the performance cost of exceptions is that of the local branch. 如果我们消除这两种影响,异常的性能成本就是本地分支的性能成本。 No matter how beautiful it sounds, that does not mean you should use Exceptions as the usual control flow, because in that case you are at the mercy of optimizing compiler! 无论它听起来多么美丽,这并不意味着你应该使用Exceptions作为通常的控制流程,因为在这种情况下你可以优化编译器! You should only use them in truly exceptional cases, where the exception frequency amortizes the possible unlucky cost of raising the actual exception. 您应该只在真正特殊情况下使用它们,其中异常频率会分摊引发实际异常的可能不幸成本。

  6. The optimistic rule-of-thumb seems to be 10^-4 frequency for exceptions is exceptional enough. 对于异常,乐观的经验法则似乎是10 ^ -4频率。 That, of course, depends on the heavy-weights of the exceptions themselves, the exact actions taken in exception handlers, etc. 当然,这取决于异常本身的重量级,异常处理程序中采取的确切操作等。

The upshot is that when an exception isn't thrown, you don't pay a cost, so when the exceptional condition is sufficiently rare exception handling is faster than using an if every time. 结果是,当没有抛出异常时,您不需要支付成本,因此当异常情况非常罕见时,异常处理比每次使用if都要快。 The full post is very much worth a read. 完整的帖子非常值得一读。


#4楼

Even if throwing an exception isn't slow, it's still a bad idea to throw exceptions for normal program flow. 即使抛出异常并不慢,但为正常的程序流抛出异常仍然是个坏主意。 Used this way it is analogous to a GOTO... 使用这种方式它类似于GOTO ......

I guess that doesn't really answer the question though. 我想这并没有真正回答这个问题。 I'd imagine that the 'conventional' wisdom of throwing exceptions being slow was true in earlier java versions (< 1.4). 我想象在早期的java版本(<1.4)中,抛出异常的“常规”智慧是正确的。 Creating an exception requires that the VM create the entire stack trace. 创建异常需要VM创建整个堆栈跟踪。 A lot has changed since then in the VM to speed things up and this is likely one area that has been improved. 从那以后,在VM中发生了很多变化,以加快速度,这可能是一个已经改进的领域。


#5楼

Why should exceptions be any slower than normal returns? 为什么例外要比正常回报慢?

As long as you don't print the stacktrace to the terminal, save it into a file or something similar, the catch-block doesn't do any more work than other code-blocks. 只要您不将堆栈跟踪打印到终端,将其保存到文件或类似文件中,catch-block就不会比其他代码块做更多的工作。 So, I can't imagine why "throw new my_cool_error()" should be that slow. 所以,我无法想象为什么“抛出新的my_cool_error()”应该那么慢。

Good question and I'm looking forward to further information on this topic! 好问题,我期待有关这个主题的进一步信息!


#6楼

I think the first article refer to the act of traversing the call stack and creating a stack trace as being the expensive part, and while the second article doesn't say it, I think that is the most expensive part of object creation. 我认为第一篇文章是指遍历调用堆栈并创建堆栈跟踪作为昂贵部分的行为,虽然第二篇文章没有说明,但我认为这是对象创建中最昂贵的部分。 John Rose has an article where he describes different techniques for speeding up exceptions . 约翰罗斯有一篇文章,他描述了加速异常的不同技术 (Preallocating and reusing an exception, exceptions without stack traces, etc) (预分配和重用异常,没有堆栈跟踪的异常等)

But still - I think this should be considered only a necessary evil, a last resort. 但仍然 - 我认为这应该只是一个必要的邪恶,最后的手段。 John's reason for doing this is to emulate features in other languages which aren't (yet) available in the JVM. John这样做的原因是模拟JVM中尚未提供的其他语言的功能。 You should NOT get into the habit of using exceptions for control flow. 你不应该养成使用控制流异常的习惯。 Especially not for performance reasons! 特别是出于性能原因! As you yourself mention in #2, you risk masking serious bugs in your code this way, and it will be harder to maintain for new programmers. 正如你自己在#2中提到的那样,你冒着以这种方式掩盖代码中的严重错误的风险,并且对新程序员来说维护起来会更困难。

Microbenchmarks in Java are surprisingly hard to get right (I've been told), especially when you get into JIT territory, so I really doubt that using exceptions is faster than "return" in real life. Java中的微型计算机难以理解(我被告知),特别是当你进入JIT领域时,所以我真的怀疑使用异常比现实生活中的“返回”更快。 For instance, I suspect you have somewhere between 2 and 5 stack frames in your test? 例如,我怀疑你的测试中有2到5个堆栈帧? Now imagine your code will be invoked by a JSF component deployed by JBoss. 现在假设您的代码将由JBoss部署的JSF组件调用。 Now you might have a stack trace which is several pages long. 现在您可能有几页长的堆栈跟踪。

Perhaps you could post your test code? 也许你可以发布你的测试代码?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值