Java异常有多慢? [英] How slow are Java exceptions?

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

问题描述

问题:Java中的异常处理实际上是否缓慢?



传统的智慧以及大量的Google结果表明,不应该使用异常逻辑Java中的正常程序流程。通常给出两个原因:


  1. 它的速度非常慢 - 甚至比常规代码慢一个数量级(给出的原因有所不同)


  1. 凌乱,因为人们期望只能以特殊代码处理错误。

这个问题是#1。



举个例子,一个href =http://leepoint.net/notes-java/flow/exceptions/03exceptions.html =noreferrer>此页面将Java异常处理描述为非常慢,并将慢度创建异常消息字符串 - 此字符串然后用于创建抛出的异常对象,这不是很快。文章 Java中的有效异常处理说这是因为异常处理的对象创建方面的原因,从而使得抛出异常本身就慢。我的测试(使用Java 1.6.0_07,32位Linux上的Java HotSpot 10.0),其中一个原因是堆栈跟踪生成是减慢的。



表示异常处理不比常规代码慢。我尝试运行一个执行一些代码的循环中的方法。在方法结束时,我使用一个布尔值来指示是否返回或 throw 。这样实际的处理是一样的。我尝试以不同的顺序运行方法,平均我的测试时间,认为可能是JVM升温。在我的所有测试中,投掷速度至少与返回速度一样快,如果不是更快(高达3.1%)。我完全开放我的测试是错误的可能性,但是我没有看到任何代码示例,测试比较的方式,或在过去一两年中显示Java中的异常处理实际上是慢的结果



引发我的这个路径是一个API,我需要使用这个异常作为正常控制逻辑的一部分。我想纠正他们的使用,但现在我可能无法。我是否应该赞成他们的前瞻性思维?



在论文即时编译中的高效Java异常处理,作者建议,即使没有异常被抛出,异常处理程序的存在就足以阻止JIT编译器从正确优化代码,从而减缓。我还没有测试这个理论。

解决方案

这取决于异常如何实现。最简单的方法是使用setjmp和longjmp。这意味着CPU的所有寄存器都被写入堆栈(已经需要一些时间),并且可能需要创建一些其他数据...所有这一切已经在try语句中发生。 throw语句需要展开堆栈并恢复所有寄存器的值(以及VM中可能的其他值)。所以尝试和抛出同样慢,这是相当缓慢的,但是如果没有异常抛出,退出try块在大多数情况下都不需要时间(因为所有的东西都放在堆栈里,如果方法存在的话自动清理)。太阳和其他人认识到,这可能是次优的,当然虚拟机的时间越来越快。还有另外一种方式来实现异常,这使得尝试本身闪电般快速(实际上没有任何事情发生,一般来说 - 所有需要发生的事情都已经在虚拟机加载该类的时候完成了),并且使得抛出不太慢。我不知道哪个JVM使用这种新的更好的技术...



...但是你是用Java编写的,所以你的代码稍后只能在一个JVM上运行在一个具体系统上?既然如果它可能运行在任何其他平台或任何其他JVM版本(可能是任何其他供应商),谁说他们也使用快速实现?快速的比较慢,而且在所有系统上都不容易。你想保持便携?然后不要依赖例外快速。



这也是一个很大的区别,你在try块中做了什么。如果您打开一个try块,从来没有调用这个try块中的任何方法,try块将是超快的,因为JIT可以实际上像一个简单的goto一样抛出。它不需要保存堆栈状态,也不需要展开堆栈,如果抛出异常(它只需要跳转到catch处理程序)。但是,这不是你通常做的。通常你打开一个try块,然后调用一个可能会抛出异常的方法,对吧?即使你只是在你的方法中使用try块,这样做什么样的方法呢,不会调用任何其他方法?它只是计算一个数字?那么你需要什么例外呢?有更优雅的方式来规范程序流程。除了简单的数学之外,您还需要调用外部方法,这样就可以破坏本地try块的优点。



请参阅以下测试代码:

  public class Test {
int value;


public int getValue(){
return value;
}

public void reset(){
value = 0;
}

//无异常计算
public void method1(int i){
value =((value + i)/ i)< 1;
//永远不会为真
如果((i& 0xFFFFFFF)== 1000000000){
System.out.println(你永远不会看到这个!
}
}

//理论上可以抛出一个,但从不会
public void method2(int i)throws Exception {
value = (值+ i)/ i)< 1;
//永远不会为true
if((i& 0xFFFFFFF)== 1000000000){
throw new Exception();
}
}

//这个会定期抛出一个
public void method3(int i)throws Exception {
value =((value + i)/ i)< 1;
//我& 1与i& amp; 0xFFFFFFF,就用;它是
//两个整数之间的AND运算。数字的大小播放
//没有角色。 AND在32位总是AND全部32位
if((i& 0x1)== 1){
throw new Exception();
}
}

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

l = System.currentTimeMillis();
t.reset(); (i = 1; i< 100000000; i ++)
{
t.method1(i);
}
l = System.currentTimeMillis() - l;
System.out.println(
method1+ l +ms,结果为+ t.getValue()
);

l = System.currentTimeMillis();
t.reset(); (i = 1; i< 100000000; i ++)
{
try {
t.method2(i);
} catch(Exception e){
System.out.println(你永远不会看到这个!);
}
}
l = System.currentTimeMillis() - l;
System.out.println(
method2+ l +ms,结果为+ t.getValue()
);

l = System.currentTimeMillis();
t.reset(); (i = 1; i< 100000000; i ++)
{
try {
t.method3(i);
} catch(Exception e){
//这里不做任何操作,因为我们将在这里
}
}
l = System.currentTimeMillis() - l;
System.out.println(
method3+ l +ms,结果为+ t.getValue()
);
}
}

结果:

  method1花了972 ms,结果是2 
method2花了1003 ms,结果是2
method3花了66716 ms,结果是2

try块的减速度太小,无法排除混淆因素,如后台进程。但是catch块杀了所有的东西,使其慢了66倍!



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


Question: Is exception handling in Java actually slow?

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. its really slow - even an order of magnitude slower than regular code (the reasons given vary),

and

  1. its messy because people expect only errors to be handled in exceptional code.

This question is about #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." 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.

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 code sample, test comparisons, or results in the last year or two that show exception handling in Java to actually be slow.

What lead 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?

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.

解决方案

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 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...

...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.

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()
        );
    }
}

Result:

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

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!

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天全站免登陆