Luke
Senior Newbie 
I love YaBB 1G - SP1!
|
 |
«
Posted
2002-11-01 21:45:20 » |
|
I know people say that one should only use exceptions in exceptional circumstances, but there are times when it seems to me that using try & catch would make my code a lot simpler than the alternative - a number of condtional return statements. Can the cost of throwing exceptions be avoided by instead of throwing a new exception, throwing a constant that extends the class Throwable? E.g. 1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class TestThrowable { static final Throwable CONSTANT_THROWABLE= new Throwable(){};
public static void main(String[] args) { try{ throw CONSTANT_THROWABLE; }catch(Throwable t){ System.out.println("Caught the throwable!"); } } } |
Luke
|
|
|
|
leknor
|
 |
«
Reply #1 - Posted
2002-11-02 00:30:01 » |
|
I think the expensive operation is catching the exception, not how you throw it.
|
|
|
|
princec
|
 |
«
Reply #2 - Posted
2002-11-02 09:35:42 » |
|
I think the expense is also incurred at 'throw' time as the exception has to be filled with a stack trace and numerous other small tweaky things need to be done in the VM. Cas 
|
|
|
|
Games published by our own members! Check 'em out!
|
|
morbo
Senior Newbie 
|
 |
«
Reply #3 - Posted
2002-11-02 12:46:50 » |
|
A seminar at the last JavaOne covered this situation. There is still a small overhead in the try/catch handling itself, but Cas is right, most of the expense is in filling in the stack trace. A constant throwable should give you better performance.
|
|
|
|
Luke
Senior Newbie 
I love YaBB 1G - SP1!
|
 |
«
Reply #4 - Posted
2002-11-02 14:39:42 » |
|
It seems that the stack trace is created when the Throwable is created, not when it is thrown, e.g. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class TestThrowable {
static final Throwable CONSTANT_THROWABLE = new Throwable() { };
public static void main(String[] args) { try { throw CONSTANT_THROWABLE;
} catch (Throwable t) { System.out.println("Caught the throwable!"); t.printStackTrace(); } } } |
Outputs: 1 2 3
| Caught the throwable! TestThrowable$1 at TestThrowable.<clinit>(TestThrowable.java:3) |
Does this mean that if I use constant Throwables, the performance of 1 2 3 4 5
| try { doSomething(); } catch (Throwable t) { cleanUp(); } |
will be approximately the same as 1 2 3 4 5
| if (canSomething()) { doSomething(); } else { cleanUp(); } |
I.e. can I expect the overhead of the try/catch handling to be equivalent to the overhead of calling an extra method, canDoSomething(), and checking the result.
|
|
|
|
princec
|
 |
«
Reply #5 - Posted
2002-11-02 22:30:08 » |
|
That's pretty weird isn't it! I suppose I've never noticed because I only throw them where I want them... But no, the overhead of throw/catch is massively greater than calling another method. I think this will even show up in a fairly small benchmark. The Hotspot guys have designed their compiler to cater for the way it should be done; there's a lot more thinking gone in to making non-exceptional code function very fast instead of exceptional code which should theoretically be called in an exceptionally unusual circumstance. Cas 
|
|
|
|
Luke
Senior Newbie 
I love YaBB 1G - SP1!
|
 |
«
Reply #6 - Posted
2002-11-03 13:19:31 » |
|
But no, the overhead of throw/catch is massively greater than calling another method. I think this will even show up in a fairly small benchmark.
I did a quick test to see what would happen, the results were as follows: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| When there are 10000000 iterations, and an exception every 2 iterations. Using try/catch and constant Throwable: 8322ms Using if(canDoSomething()): 1663ms Using try/catch and new Throwable: 116547ms
When there are 10000000 iterations, and an exception every 5 iterations. Using try/catch and constant Throwable: 4076ms Using if(canDoSomething()): 1993ms
When there are 10000000 iterations, and an exception every 12 iterations. Using try/catch and constant Throwable: 2093ms Using if(canDoSomething()): 2233ms
When there are 10000000 iterations, and an exception every 100 iterations. Using try/catch and constant Throwable: 1252ms Using if(canDoSomething()): 2223ms |
my canDoSomething() method was: 1 2 3 4 5 6 7
| static boolean canDoSomething() { if ((number % modulus) == 1) { return false; } else { return true; } } |
This suggests that: (1) the cost of throwing exceptions can be reduced by a factor of at least 10 by using constants; (2) if exceptions are occur less frequently than 1 time in 12, then catching constant Throwables when exceptions occur is faster than calling canDoSomething() and avoiding throwing exceptions. (Obviously, this depends on how long the canDoSomething() takes to execute). But its only a microbenchmark. Luke
|
|
|
|
swpalmer
|
 |
«
Reply #7 - Posted
2002-11-05 13:57:55 » |
|
Is this simply a result of where the test condition needs to go? I mean,obiviously, there is a if( something() ) throw MyException; and the key is to avoid the test that the something happened where it will be tested every loop iteration. Could you possibly use loop lables to get the same sort of functionality? eg. 1 2 3 4 5 6 7 8 9 10 11 12 13 14
| do { special_case: do { if( something() ) break special_case;
} while(true); } while( true); |
|
|
|
|
princec
|
 |
«
Reply #8 - Posted
2002-11-05 14:32:52 » |
|
Run that lot under -server and see what happens - might be interesting... Cas 
|
|
|
|
gregorypierce
|
 |
«
Reply #9 - Posted
2002-11-05 19:34:42 » |
|
I've been using exceptions for quite a few things in the application server that I've been building for work. I haven't yet seen exceptions becoming a problem because they're only ever triggered during true error conditions when in most cases I need a stack trace going to log4j and a page going to a developer. Since I never have exceptions involved in application logic at all, I've never noticed any real issues and my application needs to churn through 14k business logic operations per second. Right now my app isn't even breaking a sweat - let alone causing any issues for the Solaris box hosting it. There may be places for improvement in the exception handling code, but is one of those situations where the benefit is so trivially small compared to having to do something that's outside the standard Java coding paradigm. So I keep listing this type of thing in the 'optimization for the sake of doing optimization' category. Heck even on my PowerBook G4 I see very little point in going against the default behavior of Java. An exception should be a rare occurrence - else it isn't an exception... its a normal part of business logic that belongs in its own method 
|
http://www.gregorypierce.comShe builds, she builds oh man When she links, she links I go crazy Cause she looks like good code but she's really a hack I think I'll run upstairs and grab a snack!
|
|
|
Games published by our own members! Check 'em out!
|
|
Luke
Senior Newbie 
I love YaBB 1G - SP1!
|
 |
«
Reply #10 - Posted
2002-11-05 21:25:55 » |
|
Run that lot under -server and see what happens - might be interesting...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| D:\Dev\test>C:\j2sdk1.4.0\bin\java -server TestThrowable2
When there are 10000000 iterations, and an exception every 2 iterations. Using try/catch and constant Throwable: 1302ms Using if(canDoSomething()): 1933ms Using try/catch and new Throwable: 90129ms
When there are 10000000 iterations, and an exception every 5 iterations. Using try/catch and constant Throwable: 1102ms Using if(canDoSomething()): 2123ms Using try/catch and new Throwable: 36482ms
When there are 10000000 iterations, and an exception every 12 iterations. Using try/catch and constant Throwable: 1162ms Using if(canDoSomething()): 2293ms Using try/catch and new Throwable: 15923ms
When there are 10000000 iterations, and an exception every 100 iterations. Using try/catch and constant Throwable: 1112ms Using if(canDoSomething()): 2213ms Using try/catch and new Throwable: 2894ms
When there are 10000000 iterations, and an exception every 1000 iterations. Using try/catch and constant Throwable: 1102ms Using if(canDoSomething()): 2203ms Using try/catch and new Throwable: 1292ms |
Not really what I would have expected, but maybe its just a quirk of the microbenchmark.
|
|
|
|
Luke
Senior Newbie 
I love YaBB 1G - SP1!
|
 |
«
Reply #11 - Posted
2002-11-05 22:04:03 » |
|
I was thinking about using exceptions in non 'exceptional circumstances' because it made the code clearer, however, I think swpalmer's suggestion of using breaks with labels solves the problem without using exceptions in a way they were not designed for.
|
|
|
|
princec
|
 |
«
Reply #12 - Posted
2002-11-06 08:23:40 » |
|
The key issue with using the language in a way that it was not intended means you are effectively abusing an instance of its implementation, namely Sun's 1.4 JRE in this case. What about IBM's VM? What about the Mac port? And Linux? They are all very unlikely to treat exceptions in the same way. Furthermore, Sun can and probably will alter their VM to make the case without exceptions faster at some point in the future. Your existing code will get slower! Agh! Cas 
|
|
|
|
opinali
Junior Newbie
Java games rock!
|
 |
«
Reply #13 - Posted
2002-11-07 10:58:17 » |
|
Another cost of exceptions is in optimizations: thr try/catch blocks will make harder for the optimizer to predict the program's control flow, especially when the exception is thrown by one method and caught by another. This will spoil inlining, constant propagation, register allocation etc., especially if the throw lives inside loops.
The JGF benchmarks revel that Sun HotSpot Server 1.4.x is pretty good with this -- so good that it detects simple loops of try/catchs (local throw, with pre-created exception) as dead code and produces infinite scores -- but the same doesn't happen with non-local throws, non-precreated exceptions, or in any case with any JDK 1.3.1 (including IBM's) or even with HotSpot Client (more likely to be used by games).
|
|
|
|
|