Karsten
Karsten

Reputation: 872

How deep should a try-catch block be in Java?

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?

Upvotes: 1

Views: 1365

Answers (4)

meriton
meriton

Reputation: 70564

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" ;-)

Upvotes: 2

bubooal
bubooal

Reputation: 621

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.

Upvotes: 0

RokL
RokL

Reputation: 2812

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.

Upvotes: -1

Saj
Saj

Reputation: 18712

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

Upvotes: 1

Related Questions