1

Due to my new Job I have work a lot with Java and I am getting into the tiny details now. Obviously Java code is about Exceptions to some extent. I was wondering:

Does the calling stack effect the performance of a try-catch block a lot? I.e. should I avoid a try around a function that calls a function that... and goes too deep?

I read that try-catch blocks only affect the performance on exception. However, does it matter of far they bubble up?

9
  • 8
    imo it is more about readability than performance. Java is pretty good an optimising. Commented Sep 4, 2013 at 11:58
  • IMHO, retrieving the stacktrace is costly but this only happen if you reach the catch block. Commented Sep 4, 2013 at 12:01
  • 1
    Why bother about micro optimization? Focus on readability of code. Such deep levels of exceptions is a sign of code smell. Change that first. Commented Sep 4, 2013 at 12:05
  • Related thread (but not 100% duplicate imho) : stackoverflow.com/questions/4280831/… Commented Sep 4, 2013 at 12:19
  • 1
    The accepted answer to this question is relevant to the question of how the depth of the call stack affects performance when an exception is thrown: stackoverflow.com/questions/299068/how-slow-are-java-exceptions Commented Sep 4, 2013 at 12:30

4 Answers 4

2

Let's measure it, shall we?

package tools.bench; import java.math.BigDecimal; public abstract class Benchmark { final String name; public Benchmark(String name) { this.name = name; } abstract int run(int iterations) throws Throwable; private BigDecimal time() { try { int nextI = 1; int i; long duration; do { i = nextI; long start = System.nanoTime(); run(i); duration = System.nanoTime() - start; nextI = (i << 1) | 1; } while (duration < 1000000000 && nextI > 0); return new BigDecimal((duration) * 1000 / i).movePointLeft(3); } catch (Throwable e) { throw new RuntimeException(e); } } @Override public String toString() { return name + "\t" + time() + " ns"; } enum ExceptionStrategy { none { @Override void run() { // do nothing } }, normal { @Override void run() { throw new RuntimeException(); } }, withoutStackTrace { @Override void run() { throw new RuntimeException() { public synchronized Throwable fillInStackTrace() { return this; }; }; } }; abstract void run(); } private static Benchmark tryBenchmark(final int depth, final ExceptionStrategy strat) { return new Benchmark("try, depth = " + depth + ", " + strat) { @Override int run(int iterations) { int x = 0; for (int i = 1; i < iterations; i++) { try { x += recurseAndThrow(depth); } catch (Exception e) { x++; } } return x; } private int recurseAndThrow(int i) { if (i > 0) { return recurseAndThrow(i - 1) + 1; } else { strat.run(); return 0; } } }; } public static void main(String[] args) throws Exception { int[] depths = {1, 10, 100, 1000, 10000}; for (int depth : depths) { for (ExceptionStrategy strat : ExceptionStrategy.values()) { System.out.println(tryBenchmark(depth, strat)); } } } } 

On my (quite dated) notebook, this prints:

try, depth = 1, none 5.153 ns try, depth = 1, normal 3374.113 ns try, depth = 1, withoutStackTrace 602.570 ns try, depth = 10, none 59.019 ns try, depth = 10, normal 9064.392 ns try, depth = 10, withoutStackTrace 3528.987 ns try, depth = 100, none 604.828 ns try, depth = 100, normal 49387.143 ns try, depth = 100, withoutStackTrace 27968.674 ns try, depth = 1000, none 5388.270 ns try, depth = 1000, normal 457158.668 ns try, depth = 1000, withoutStackTrace 271881.336 ns try, depth = 10000, none 69793.242 ns try, depth = 10000, normal 2895133.943 ns try, depth = 10000, withoutStackTrace 2728533.381 ns 

Obviously, the specific results will vary with your hardware, and JVM implementation and configuration. However, the general pattern is likely to remain the same.

Conclusions:

  • The try statement itself incurs negligible overhead.
  • Throwing an exception and unwinding the callstack incurs overhead linear in the size of the stack (or the amount of stack to unwind).
    • For stack sizes of real-world applications (let's assume 100 stack frames), that overhead is about 50 micro seconds, or 0.00005 seconds.
    • That overhead can be reduced somewhat by throwing exceptions without stack trace

Recommendatations:

  • Don't worry about the performance of try statements.
  • Don't use exceptions to signal conditions that occur frequently (say, more than 1000 times per second).
  • Otherwise, don't worry about the performance of throwing exceptions.
  • Also, "premature optimization is the root of all evil" ;-)
Sign up to request clarification or add additional context in comments.

Comments

1

Exceptions are expensive. When used, a stack trace is created. If you can check for an exception, do so. Don't use try..catch for flow control. When you cannot check/validate, use try..catch; an example would be doing IO operations.

When I see code with lots of try..catch blocks, my immediate thought is "This is a bad design!".

9 Comments

Change the you check for an exception to avoid the exception if possible.
Ironically that is not always true. Exceptions are in some cases faster. Using them for control flow is generally eschewed for semantic and coherent reasons.
It depends. Exceptions are relatively inexpensive for most software. If the implemented feature is performance heavy, then exceptions should be avoided (e.g. in games), but for a web application where loops happen at most a few hundred times, throwing exceptions is much better than handling error codes and nulls.
Your answer is a personal opinion and not backed up by hard data.
The last statement is indeed the personal part. But claiming that exceptions are expensive should be backed by data proving this. In my personal experience exceptions seem to have little to no impact, though they should be avoided.
|
0

1.- Calling stack depth is nothing you should worry about, Java Just In Time Compiler will make optimizations for your code like method inlining to provide the best performance on your code.

2.- Catch blocks do affect performance, this is because catching an exception might mean several different operations inside the JVM like going through the exception table, stack unwinding, and common traps (deoptimize JIT's optimized code).

3.- As a piece of advice, do not worry about encapsulating code and ending up with several method calls which would traduce to a huge stack depth, the JVM will make optimizations to get the best performance out of this when your application is compiled and when it is running so always aim for code readability and good design patterns since this will help make your code easier to maintain and easier to modify in case some performance fix has to kick in. And about catch blocks, evaluate how necessary it is to have an exception thrown or catched, and if you are catching try to catch the most general exception this so you can avoid huge exceptions tables.

Comments

-1

Stack trace is not loaded until you call either .printStackTrace or .getStackTrace. If you put a breakpoint at the start of a catch block you'll notice that Exception object has null stack trace.

1 Comment

AFAIK that's not true. Stack is "recorded" when Exception is created, see fillInStackTrace() call in Throwable constructor. But those methods are native and possibly performance heavy. stackTrace is null, because lazy initialization is used here. Further you one see, that stack trace is created by using other native methods as getStackTraceDepth() and getStackTraceElement(int) in getOurStackTrace().

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.