Reputation: 872
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
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:
Recommendatations:
Upvotes: 2
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
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
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