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