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

查看:106
本文介绍了异常对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有关。

例如,< href = http://leepoint.net/notes-java/flow/exceptions/03exceptions.html rel = noreferrer>此页面将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和其他人认识到,这可能不是最佳选择,并且随着时间的流逝,虚拟机当然会越来越快。还有另一种实现异常的方法,它使尝试自身的运行速度很快(实际上,尝试完全没有任何反应-当类由VM加载时,所有需要发生的事情都已经完成了),而且抛出异常的速度也不太慢。我不知道哪个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。如果抛出异常(它只需要跳转到catch处理程序),则既不需要保存堆栈状态,也不需要取消堆栈。但是,这不是您通常执行的操作。通常,您打开一个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.

请参见以下测试代码:

See the following test code:

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并扔进去,结果并不会那么糟糕。相同的方法(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天全站免登陆