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