Using exceptions for normal flow of code is bad - it's slow, it's bad code quality, and we've all had this drummed into our heads from day 1. At least I have! This makes sense as well, exceptions generate stack traces each time they're called, stack traces take a long time to generate and thus an exception being thrown and caught is mu开发者_如何转开发ch slower than an equivalent if statement.
So I decided to make a quick example to demonstrate this.
public static void main(String[] args) throws Exception {
Object[] objs = new Object[500000];
for (int i = 0; i < objs.length; i++) {
if (Math.random() < 0.5) {
objs[i] = new Object();
}
}
long cur = System.currentTimeMillis();
for (Object o : objs) {
try {
System.out.print(o.getClass());
}
catch (Exception e) {
System.out.print("null");
}
}
System.err.println("Try / catch: "+(System.currentTimeMillis() - cur));
cur = System.currentTimeMillis();
for (Object o : objs) {
if(o==null) {
System.out.print("null");
}
else {
System.out.print(o.getClass());
}
}
System.err.println("If: "+(System.currentTimeMillis() - cur));
}
However, on running the code I was shocked to see the following:
Try / catch: 11204
If: 11953
I ran the code again, this time "if" was faster:
Try / catch: 12188
If: 12187
...but only by a ms.
This is all run on the server VM, I know the majority of the time will be taken up by the print()
statements, and some runs show if as a bit faster than try / catch. But regardless, surely they shouldn't even be close? And how on earth could generating stack traces be faster than a single if statement?
EDIT: To clarify, this question is a purely academic one - I know full well using exceptions for the normal flow of code is bad and would never do so!
This was discussed recently in the Java Specialists newletter: http://www.javaspecialists.eu/archive/Issue187.html
Short answer: it's not generating the stack trace each time, but caching an instance of the exception.
Because the exception throw point and catch point are in the same scope, and the JIT can prove that you never actually look at the stack trace, it's possible to optimize the exception into a single branch. You may be able to avoid this by explicitly throwing an exception of custom type, ideally across a function call boundary. To control against the allocation cost, in the non-throw case be sure to construct an instance of said exception object anyway (and ideally return it, to ensure escape analysis can't convert it to a stack allocation).
That said, the JIT can do all kinds of weird things. It's best to try a test case as similar as possible to Real Code, not an artificial case like this, so you can see the benchmark results after any optimizations the JIT can apply to your real code.
I believe the stack traces used to be painfully slow in the earlier version of Java. In fact, I wrote a program that looped a UK grid map 1000 x 800, and using if statements, the program ran in 10 minutes, running within exceptions and catching, it ran in 2 hours. This was 10 years ago.
More recently, the JVM has got much more efficient at dealing with try-catch.
I also had, "exceptions are for exceptional circumstances" drummed into me from the beginning, and when I saw that the play framework used exceptions as a global goto statement, to execute the flow of control, I was initially shocked. But, running Play in real work scenarios and it is really fast. It was another misconception of mine entirely blown away, and open my mind to many new ways of using exceptions as a design pattern.
The basic reason is that the population of the stack trace data is now delayed until actually needed, where originally it was populated when the exception was created.
You may want to print your stacktrace to a ByteArrayOutputStream to see the siutation where the stacktrace is actually used for something.
Its worth noting that, while many people consider using exceptions for flow control as "bad form", it's not necessarily slower. For example, you may have a choice between
- testing whether a value exists on each iteration
- assuming the value exists and throwing an exception when it doesn't
For cases where the value exists less often, the test-each-iteration version will likely be faster. For cases where the value exists more often, the catch-the-failure version will likely be faster. Admittedly, making the choice between the two assumes you have a good understanding of your data and, hopefully, can run a profiler to see what the impact is of switching between the two.
All that being said, if you don't actually know that an thrown exception will be faster, you're probably better off going with the test-each-iteration version, since it's (for many people) more obvious what it's doing.
few things
- the benchmark test is flawed Badly.
- catch/throwing java exception is fast, very fast
- creating and gathering the stack trace is slower (but hotspots annotates the stack)
That being said: exceptions are not slow. Printing their stack trace is another issue.
Extra bonus: null checks are fast, so are the null-traps when not using the exception
However, the speed is not the only reason for not using exceptions in the normal flow. Exceptions are a mechanism to indicate that the contract of a method is broken. If you start using exceptions as flow control then it defeats this purpose. Usually you can write a few lines to code that will avoid the exception.
精彩评论