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?

Karsten
  • 882
  • 6
  • 18
  • 8
    imo it is more about readability than performance. Java is pretty good an optimising. – RNJ Sep 04 '13 at 11:58
  • IMHO, retrieving the stacktrace is costly but this only happen if you reach the catch block. – Arnaud Denoyelle Sep 04 '13 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. – Narendra Pathai Sep 04 '13 at 12:05
  • Related thread (but not 100% duplicate imho) : http://stackoverflow.com/questions/4280831/java-try-catch-performance-is-it-recommended-to-keep-what-is-inside-the-try-cla – Arnaud Denoyelle Sep 04 '13 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: http://stackoverflow.com/questions/299068/how-slow-are-java-exceptions – yiannis Sep 04 '13 at 12:30
  • If you're having exceptions thrown often enough to worry about the associated performance then somethig is seriously wrong with your program. Exceptions should be "exceptional" – Richard Tingle Sep 04 '13 at 13:39
  • @yiannis I found that answer before asking. It is related but talks about calling functions after one another. In that case it is about readability as RNJ states, I guess. Richard Tingle: Thanks for the link. – Karsten Sep 04 '13 at 15:01
  • You handle exception because you expect them to happen, I think you have enough inputs, only thing that I would add is if it happens you should be able to understand what and why happened from the logs. – Sachin Thapa Sep 04 '13 at 21:10
  • "Java code is about exceptions to some extent", well I think good Java code is more about letting exceptions do their thing and not catch them too early... You should quite often have just `try...finally` blocks without `catch` part, and you should avoid catching and re-throwing unless you actually can add some information to the exception, that is useful for real. Also, learn and use the features added to Java 7, which make some exception stuff much nicer. – hyde Sep 05 '13 at 06:53

4 Answers4

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" ;-)
meriton
  • 68,356
  • 14
  • 108
  • 175
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!".

Sajal Dutta
  • 18,272
  • 11
  • 52
  • 74
  • Change *the you check for an exception* to **avoid the exception if possible**. – Narendra Pathai Sep 04 '13 at 12:06
  • 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. – Adam Gent Sep 04 '13 at 12:08
  • 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. – allprog Sep 04 '13 at 12:09
  • If the exception block is due to "I didn't know what else to do..", is a bad program/design. I like this paper: http://www.oracle.com/technetwork/java/effective-exceptions-092345.html – Sajal Dutta Sep 04 '13 at 12:18
  • 1
    Your answer is a personal opinion and not backed up by hard data. – tom Sep 04 '13 at 13:22
  • @tom Aside from the last statement in answer, which do you see as personal opinion? – Sajal Dutta Sep 04 '13 at 13:36
  • 1
    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. – tom Sep 04 '13 at 13:41
  • @tom That staement is follwed by "When used, a stack trace is created". What did you not get? – Sajal Dutta Sep 04 '13 at 13:48
  • I am not going to use it for flow control. This is another debate. – Karsten Sep 04 '13 at 15:02
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.

bubooal
  • 621
  • 3
  • 8
-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.

RokL
  • 2,663
  • 3
  • 22
  • 26
  • 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()`. – Betlista Sep 04 '13 at 13:30