异常对 Java 性能的影响是什么? [英] What are the effects of exceptions on performance in Java?

查看:27
本文介绍了异常对 Java 性能的影响是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

问题:Java 中的异常处理真的很慢吗?

Question: Is exception handling in Java actually slow?

传统观点以及 Google 的大量结果都表明,不应将异常逻辑用于 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. Two reasons are usually given,

  1. 它真的很慢 - 甚至比普通代码慢一个数量级(给出的原因各不相同),

  1. 它很混乱,因为人们期望在异常代码中只处理错误.

这个问题是关于#1.

例如,此页面将 Java 异常处理描述为非常慢"并将缓慢与异常消息字符串的创建联系起来 - 然后该字符串用于创建抛出的异常对象.这并不快."文章 Java 中的有效异常处理说这样做的原因是由于异常处理的对象创建方面,这使得抛出异常本质上很慢".另一个原因是堆栈跟踪的生成会减慢它的速度.

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." 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". Another reason out there is that the stack trace generation is what slows it down.

我的测试(在 32 位 Linux 上使用 Java 1.6.0_07、Java HotSpot 10.0)表明异常处理并不比常规代码慢.我尝试在执行一些代码的循环中运行一个方法.在方法的最后,我使用一个布尔值来指示是 return 还是 throw.这样实际的处理是一样的.我尝试以不同的顺序运行这些方法并平均我的测试时间,认为这可能是 JVM 预热.在我所有的测试中,如果不是更快(最多快 3.1%),投掷至少与返回一样快.我对我的测试错误的可能性完全持开放态度,但在过去一两年的代码示例、测试比较或结果方面,我没有看到任何表明 Java 中的异常处理实际上是错误的慢.

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. 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. In all my tests, the throw was at least as fast as the return, if not faster (up to 3.1% faster). 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.

让我走上这条道路的是我需要使用的 API,它抛出异常作为正常控制逻辑的一部分.我想更正它们的用法,但现在我可能无法做到.相反,我是否必须赞扬他们的前瞻性思维?

What leads me down this path was an API I needed to use that threw exceptions as part of normal control logic. 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?

在论文实时编译中的高效 Java 异常处理,作者建议单独的异常处理程序的存在,即使没有抛出异常,也足以阻止 JIT 编译器正确优化代码,从而减慢它的速度.我还没有测试过这个理论.

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. I haven't tested this theory yet.

推荐答案

这取决于异常是如何实现的.最简单的方法是使用 setjmp 和 longjmp.这意味着 CPU 的所有寄存器都被写入堆栈(这已经需要一些时间)并且可能需要创建一些其他数据......所有这些都已经在 try 语句中发生了.throw 语句需要展开堆栈并恢复所有寄存器的值(以及 VM 中可能的其他值).所以 try 和 throw 同样慢,而且相当慢,但是如果没有抛出异常,在大多数情况下退出 try 块不需要任何时间(因为所有东西都放在堆栈上,如果方法存在,堆栈会自动清理).

It depends how exceptions are implemented. The simplest way is using setjmp and longjmp. That means all registers of the CPU are written to the stack (which already takes some time) and possibly some other data needs to be created... all this already happens in the try statement. The throw statement needs to unwind the stack and restore the values of all registers (and possible other values in the VM). So try and throw are equally slow, and that is pretty slow, however if no exception is thrown, exiting the try block takes no time whatsoever in most cases (as everything is put on the stack which cleans up automatically if the method exists).

Sun 和其他人认识到,这可能不是最理想的,当然,随着时间的推移,虚拟机变得越来越快.还有另一种实现异常的方法,它使 try 本身闪电般快速(实际上一般来说 try 什么都没有发生 - 需要发生的一切在 VM 加载类时已经完成)并且它使 throw 不那么慢.我不知道哪个 JVM 使用这种新的、更好的技术......

Sun and others recognized, that this is possibly suboptimal and of course VMs get faster and faster over the time. There is another way to implement exceptions, which makes try itself lightning fast (actually nothing happens for try at all in general - everything that needs to happen is already done when the class is loaded by the VM) and it makes throw not quite as slow. I don't know which JVM uses this new, better technique...

...但是你是用 Java 编写的,所以你的代码以后只能在一个特定系统上的一个 JVM 上运行?既然它可以在任何其他平台或任何其他 JVM 版本(可能是任何其他供应商)上运行,那么谁说他们也使用快速实现?快速的比慢的更复杂,并且在所有系统上都不容易实现.你想保持便携吗?然后不要依赖异常的速度.

...but are you writing in Java so your code later on only runs on one JVM on one specific system? Since if it may ever run on any other platform or any other JVM version (possibly of any other vendor), who says they also use the fast implementation? The fast one is more complicated than the slow one and not easily possible on all systems. You want to stay portable? Then don't rely on exceptions being fast.

在 try 块中执行的操作也有很大的不同.如果您打开一个 try 块并且从不从该 try 块中调用任何方法,则 try 块将非常快,因为 JIT 实际上可以像简单的 goto 一样处理 throw.它既不需要保存堆栈状态,也不需要在抛出异常时展开堆栈(它只需要跳转到捕获处理程序).但是,这不是您通常所做的.通常你打开一个 try 块,然后调用一个可能抛出异常的方法,对吧?即使你只是在你的方法中使用 try 块,这将是一种什么样的方法,不调用任何其他方法?它只会计算一个数字吗?那你为什么需要例外呢?有许多更优雅的方法来调节程序流程.对于除简单数学之外的其他几乎所有事情,您都必须调用外部方法,这已经破坏了本地 try 块的优势.

It also makes a big difference what you do within a try block. If you open a try block and never call any method from within this try block, the try block will be ultra fast, as the JIT can then actually treat a throw like a simple goto. It neither needs to save stack-state nor does it need to unwind the stack if an exception is thrown (it only needs to jump to the catch handlers). However, this is not what you usually do. Usually you open a try block and then call a method that might throw an exception, right? And even if you just use the try block within your method, what kind of method will this be, that does not call any other method? Will it just calculate a number? Then what for do you need exceptions? There are much more elegant ways to regulate program flow. For pretty much anything else but simple math, you will have to call an external method and this already destroys the advantage of a local try block.

查看以下测试代码:

public class Test {
    int value;


    public int getValue() {
        return value;
    }

    public void reset() {
        value = 0;
    }

    // Calculates without exception
    public void method1(int i) {
        value = ((value + i) / i) << 1;
        // Will never be true
        if ((i & 0xFFFFFFF) == 1000000000) {
            System.out.println("You'll never see this!");
        }
    }

    // Could in theory throw one, but never will
    public void method2(int i) throws Exception {
        value = ((value + i) / i) << 1;
        // Will never be true
        if ((i & 0xFFFFFFF) == 1000000000) {
            throw new Exception();
        }
    }

    // This one will regularly throw one
    public void method3(int i) throws Exception {
        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 Exception();
        }
    }

    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++) {
            t.method1(i);
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method1 took " + l + " ms, result was " + t.getValue()
        );

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            try {
                t.method2(i);
            } catch (Exception e) {
                System.out.println("You'll never see this!");
            }
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method2 took " + l + " ms, result was " + t.getValue()
        );

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

结果:

method1 took 972 ms, result was 2
method2 took 1003 ms, result was 2
method3 took 66716 ms, result was 2

try 块的减速太小,无法排除后台进程等混杂因素.但是 catch 块杀死了一切,使它慢了 66 倍!

The slowdown from the try block is too small to rule out confounding factors such as background processes. But the catch block killed everything and made it 66 times slower!

正如我所说,如果你把 try/catch 和 throw all 放在同一个方法(method3)中,结果不会那么糟糕,但这是一个我不会依赖的特殊 JIT 优化.即使使用这种优化,抛出仍然很慢.所以我不知道你在这里想做什么,但肯定有比使用 try/catch/throw 更好的方法.

As I said, the result will not be that bad if you put try/catch and throw all within the same method (method3), but this is a special JIT optimization I would not rely upon. And even when using this optimization, the throw is still pretty slow. So I don't know what you are trying to do here, but there is definitely a better way of doing it than using try/catch/throw.

这篇关于异常对 Java 性能的影响是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆