What are the effects of exceptions on performance in Java?

前端 未结 17 2343
情深已故
情深已故 2020-11-22 01:21

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 n

相关标签:
17条回答
  • 2020-11-22 01:21

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

    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. For instance, I suspect you have somewhere between 2 and 5 stack frames in your test? Now imagine your code will be invoked by a JSF component deployed by JBoss. Now you might have a stack trace which is several pages long.

    Perhaps you could post your test code?

    0 讨论(0)
  • 2020-11-22 01:22

    Great post about exception performance is:

    https://shipilev.net/blog/2014/exceptional-performance/

    Instantiating vs reusing existing, with stack trace and without, etc:

    Benchmark                            Mode   Samples         Mean   Mean error  Units
    
    dynamicException                     avgt        25     1901.196       14.572  ns/op
    dynamicException_NoStack             avgt        25       67.029        0.212  ns/op
    dynamicException_NoStack_UsedData    avgt        25       68.952        0.441  ns/op
    dynamicException_NoStack_UsedStack   avgt        25      137.329        1.039  ns/op
    dynamicException_UsedData            avgt        25     1900.770        9.359  ns/op
    dynamicException_UsedStack           avgt        25    20033.658      118.600  ns/op
    
    plain                                avgt        25        1.259        0.002  ns/op
    staticException                      avgt        25        1.510        0.001  ns/op
    staticException_NoStack              avgt        25        1.514        0.003  ns/op
    staticException_NoStack_UsedData     avgt        25        4.185        0.015  ns/op
    staticException_NoStack_UsedStack    avgt        25       19.110        0.051  ns/op
    staticException_UsedData             avgt        25        4.159        0.007  ns/op
    staticException_UsedStack            avgt        25       25.144        0.186  ns/op
    

    Depending on depth of stack trace:

    Benchmark        Mode   Samples         Mean   Mean error  Units
    
    exception_0000   avgt        25     1959.068       30.783  ns/op
    exception_0001   avgt        25     1945.958       12.104  ns/op
    exception_0002   avgt        25     2063.575       47.708  ns/op
    exception_0004   avgt        25     2211.882       29.417  ns/op
    exception_0008   avgt        25     2472.729       57.336  ns/op
    exception_0016   avgt        25     2950.847       29.863  ns/op
    exception_0032   avgt        25     4416.548       50.340  ns/op
    exception_0064   avgt        25     6845.140       40.114  ns/op
    exception_0128   avgt        25    11774.758       54.299  ns/op
    exception_0256   avgt        25    21617.526      101.379  ns/op
    exception_0512   avgt        25    42780.434      144.594  ns/op
    exception_1024   avgt        25    82839.358      291.434  ns/op
    

    For other details (including x64 assembler from JIT) read original blog post.

    That mean Hibernate/Spring/etc-EE-shit are slow because of exceptions (xD) and rewriting app control flow away from exceptions (replace it with continure / break and returning boolean flags like in C from method call) improve performance of your application 10x-100x, depending on how often you throws them ))

    0 讨论(0)
  • 2020-11-22 01:22

    My opinion about Exception speed versus checking data programmatically.

    Many classes had String to value converter (scanner / parser), respected and well-known libraries too ;)

    usually has form

    class Example {
    public static Example Parse(String input) throws AnyRuntimeParsigException
    ...
    }
    

    exception name is only example, usually is unchecked (runtime), so throws declaration is only my picture

    sometimes exist second form:

    public static Example Parse(String input, Example defaultValue)
    

    never throwing

    When the second ins't available (or programmer read too less docs and use only first), write such code with regular expression. Regular expression are cool, politically correct etc:

    Xxxxx.regex(".....pattern", src);
    if(ImTotallySure)
    {
      Example v = Example.Parse(src);
    }
    

    with this code programmers hasn't cost of exceptions. BUT HAS comparable very HIGH cost of regular expressions ALWAYS versus small cost of exception sometimes.

    I use almost always in such context

    try { parse } catch(ParsingException ) // concrete exception from javadoc
    {
    }
    

    without analysing stacktrace etc, I believe after lectures of Yours quite speed.

    Do not be afraid Exceptions

    0 讨论(0)
  • 2020-11-22 01:23

    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.

    0 讨论(0)
  • 2020-11-22 01:27

    Exception performance in Java and C# leaves much to be desired.

    As programmers this forces us to live by the rule "exceptions should be caused infrequently", simply for practical performance reasons.

    However, as computer scientists, we should rebel against this problematic state. The person authoring a function often has no idea how often it will be called, or whether success or failure is more likely. Only the caller has this information. Trying to avoid exceptions leads to unclear API idoms where in some cases we have only clean-but-slow exception versions, and in other cases we have fast-but-clunky return-value errors, and in still other cases we end up with both. The library implementor may have to write and maintain two versions of APIs, and the caller has to decide which of two versions to use in each situation.

    This is kind of a mess. If exceptions had better performance, we could avoid these clunky idioms and use exceptions as they were meant to be used... as a structured error return facility.

    I'd really like to see exception mechanisms implemented using techniques closer to return-values, so we could have performance closer to return values.. since this is what we revert to in performance sensitive code.

    Here is a code-sample that compares exception performance to error-return-value performance.

    public class TestIt {

    int value;
    
    
    public int getValue() {
        return value;
    }
    
    public void reset() {
        value = 0;
    }
    
    public boolean baseline_null(boolean shouldfail, int recurse_depth) {
        if (recurse_depth <= 0) {
            return shouldfail;
        } else {
            return baseline_null(shouldfail,recurse_depth-1);
        }
    }
    
    public boolean retval_error(boolean shouldfail, int recurse_depth) {
        if (recurse_depth <= 0) {
            if (shouldfail) {
                return false;
            } else {
                return true;
            }
        } else {
            boolean nested_error = retval_error(shouldfail,recurse_depth-1);
            if (nested_error) {
                return true;
            } else {
                return false;
            }
        }
    }
    
    public void exception_error(boolean shouldfail, int recurse_depth) throws Exception {
        if (recurse_depth <= 0) {
            if (shouldfail) {
                throw new Exception();
            }
        } else {
            exception_error(shouldfail,recurse_depth-1);
        }
    
    }
    
    public static void main(String[] args) {
        int i;
        long l;
        TestIt t = new TestIt();
        int failures;
    
        int ITERATION_COUNT = 100000000;
    
    
        // (0) baseline null workload
        for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
            for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
                int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            
    
                failures = 0;
                long start_time = System.currentTimeMillis();
                t.reset();              
                for (i = 1; i < ITERATION_COUNT; i++) {
                    boolean shoulderror = (i % EXCEPTION_MOD) == 0;
                    t.baseline_null(shoulderror,recurse_depth);
                }
                long elapsed_time = System.currentTimeMillis() - start_time;
                System.out.format("baseline: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n",
                        recurse_depth, exception_freq, failures,elapsed_time);
            }
        }
    
    
        // (1) retval_error
        for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
            for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
                int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            
    
                failures = 0;
                long start_time = System.currentTimeMillis();
                t.reset();              
                for (i = 1; i < ITERATION_COUNT; i++) {
                    boolean shoulderror = (i % EXCEPTION_MOD) == 0;
                    if (!t.retval_error(shoulderror,recurse_depth)) {
                        failures++;
                    }
                }
                long elapsed_time = System.currentTimeMillis() - start_time;
                System.out.format("retval_error: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n",
                        recurse_depth, exception_freq, failures,elapsed_time);
            }
        }
    
        // (2) exception_error
        for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
            for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
                int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            
    
                failures = 0;
                long start_time = System.currentTimeMillis();
                t.reset();              
                for (i = 1; i < ITERATION_COUNT; i++) {
                    boolean shoulderror = (i % EXCEPTION_MOD) == 0;
                    try {
                        t.exception_error(shoulderror,recurse_depth);
                    } catch (Exception e) {
                        failures++;
                    }
                }
                long elapsed_time = System.currentTimeMillis() - start_time;
                System.out.format("exception_error: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n",
                        recurse_depth, exception_freq, failures,elapsed_time);              
            }
        }
    }
    

    }

    And here are the results:

    baseline: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 683 ms
    baseline: recurse_depth 2, exception_freqeuncy 0.25 (0), time elapsed 790 ms
    baseline: recurse_depth 2, exception_freqeuncy 0.5 (0), time elapsed 768 ms
    baseline: recurse_depth 2, exception_freqeuncy 0.75 (0), time elapsed 749 ms
    baseline: recurse_depth 2, exception_freqeuncy 1.0 (0), time elapsed 731 ms
    baseline: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 923 ms
    baseline: recurse_depth 5, exception_freqeuncy 0.25 (0), time elapsed 971 ms
    baseline: recurse_depth 5, exception_freqeuncy 0.5 (0), time elapsed 982 ms
    baseline: recurse_depth 5, exception_freqeuncy 0.75 (0), time elapsed 947 ms
    baseline: recurse_depth 5, exception_freqeuncy 1.0 (0), time elapsed 937 ms
    baseline: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1154 ms
    baseline: recurse_depth 8, exception_freqeuncy 0.25 (0), time elapsed 1149 ms
    baseline: recurse_depth 8, exception_freqeuncy 0.5 (0), time elapsed 1133 ms
    baseline: recurse_depth 8, exception_freqeuncy 0.75 (0), time elapsed 1117 ms
    baseline: recurse_depth 8, exception_freqeuncy 1.0 (0), time elapsed 1116 ms
    retval_error: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 742 ms
    retval_error: recurse_depth 2, exception_freqeuncy 0.25 (24999999), time elapsed 743 ms
    retval_error: recurse_depth 2, exception_freqeuncy 0.5 (49999999), time elapsed 734 ms
    retval_error: recurse_depth 2, exception_freqeuncy 0.75 (99999999), time elapsed 723 ms
    retval_error: recurse_depth 2, exception_freqeuncy 1.0 (99999999), time elapsed 728 ms
    retval_error: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 920 ms
    retval_error: recurse_depth 5, exception_freqeuncy 0.25 (24999999), time elapsed 1121   ms
    retval_error: recurse_depth 5, exception_freqeuncy 0.5 (49999999), time elapsed 1037 ms
    retval_error: recurse_depth 5, exception_freqeuncy 0.75 (99999999), time elapsed 1141   ms
    retval_error: recurse_depth 5, exception_freqeuncy 1.0 (99999999), time elapsed 1130 ms
    retval_error: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1218 ms
    retval_error: recurse_depth 8, exception_freqeuncy 0.25 (24999999), time elapsed 1334  ms
    retval_error: recurse_depth 8, exception_freqeuncy 0.5 (49999999), time elapsed 1478 ms
    retval_error: recurse_depth 8, exception_freqeuncy 0.75 (99999999), time elapsed 1637 ms
    retval_error: recurse_depth 8, exception_freqeuncy 1.0 (99999999), time elapsed 1655 ms
    exception_error: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 726 ms
    exception_error: recurse_depth 2, exception_freqeuncy 0.25 (24999999), time elapsed 17487   ms
    exception_error: recurse_depth 2, exception_freqeuncy 0.5 (49999999), time elapsed 33763   ms
    exception_error: recurse_depth 2, exception_freqeuncy 0.75 (99999999), time elapsed 67367   ms
    exception_error: recurse_depth 2, exception_freqeuncy 1.0 (99999999), time elapsed 66990 ms
    exception_error: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 924 ms
    exception_error: recurse_depth 5, exception_freqeuncy 0.25 (24999999), time elapsed 23775  ms
    exception_error: recurse_depth 5, exception_freqeuncy 0.5 (49999999), time elapsed 46326 ms
    exception_error: recurse_depth 5, exception_freqeuncy 0.75 (99999999), time elapsed 91707 ms
    exception_error: recurse_depth 5, exception_freqeuncy 1.0 (99999999), time elapsed 91580 ms
    exception_error: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1144 ms
    exception_error: recurse_depth 8, exception_freqeuncy 0.25 (24999999), time elapsed 30440 ms
    exception_error: recurse_depth 8, exception_freqeuncy 0.5 (49999999), time elapsed 59116   ms
    exception_error: recurse_depth 8, exception_freqeuncy 0.75 (99999999), time elapsed 116678 ms
    exception_error: recurse_depth 8, exception_freqeuncy 1.0 (99999999), time elapsed 116477 ms
    

    Checking and propagating return-values does add some cost vs the baseline-null call, and that cost is proportional to call-depth. At a call-chain depth of 8, the error-return-value checking version was about 27% slower than the basline version which did not check return values.

    Exception performance, in comparison, is not a function of call-depth, but of exception frequency. However, the degredation as exception frequency increases is much more dramatic. At only a 25% error frequency, the code ran 24-TIMES slower. At an error frequency of 100%, the exception version is almost 100-TIMES slower.

    This suggests to me that perhaps are making the wrong tradeoffs in our exception implementations. Exceptions could be faster, either by avoiding costly stalk-walks, or by outright turning them into compiler supported return-value checking. Until they do, we're stuck avoiding them when we want our code to run fast.

    0 讨论(0)
  • 2020-11-22 01:29

    HotSpot is quite capable of removing exception code for system generated exceptions, so long as it is all inlined. However, explicitly created exception and those otherwise not removed spend a lot of time creating the stack trace. Override fillInStackTrace to see how this can affect performance.

    0 讨论(0)
提交回复
热议问题