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