Hacker News new | past | comments | ask | show | jobs | submit login
Why Java exceptions are slow (and Common Lisp conditions aren't) (groups.google.com)
77 points by mdasen on Jan 17, 2009 | hide | past | favorite | 34 comments



FWIW, structured exception handling (SEH) on Windows (and used by .NET etc.) does not use the approach described herein.

SEH scans the stack twice. The first time, it calls handlers in reverse order of registration (handlers are registered on function entry) asking them if they handle this particular exception. Any handler can return a condition code to tell the OS to essentially ignore the exception and continue with the next instruction (a bit like VB 'On Error Resume Next'); perhaps the handler repaired the error condition.

It's only after it has found a handler that indicates that it can handle the given exception that the OS goes back and actually unwinds the stack. That process involves calling all the handlers a second time, only this time passing them a flag indicating that unwinding is actually taking place. That's when 'finally' blocks get run. The OS keeps on going until it reaches the handler that indicated it could handle the exception.

That's how things work in Win32. The situation in Win64 is slightly different, as rather than FS:0 containing a pointer to the head of the exception chain, program-counter-based lookup tables are used instead. However, the two-phase exception dispatch is still used.


An exception, should be - by definition - exceptional. Why would it matter that much if it's slow? I think that's premature optimization...

There are a lot more important things to make sure are fast.

If most of your CPU time is spent throwing Exceptions, something is probably seriously wrong.


The title of this HN submission is subtly misleading. The original post discusses the fact that Java's exceptions are slow, but merely as a symptom of the main problem: Java mixes up the issues of (a) finding some code that knows how to handle an exceptional condition and (b) backing up the call stack until it can find some code to continue executing. In Java, (b) is unavoidable, so the context of every exception is destroyed before the exception handler can examine it.

As noted elsewhere in this discussion, this means that Java exceptions aren't resumable [1] and that a Java exception can't examine all the variables that were alive when the exception was thrown and take action (like: logging) based on the values of those variables [2]. Plus there may be memory-usage implications. All of which are dimensions of suck that are independent of the speed issue.

---

[1] Not that I really know what this means, having never learned a language where this was possible.

[2] This, on the other hand, would be win that I could understand.


Couldn't [1] be implemented quite simply by some sort of exception handler manager thinggie?

Exception.OnThrown += LogThrownException;

Where this callback has some signature which can examine the stack, locals, and potentially resume?


By doing this you will get something highly similar to CL's conditions. In CL, there is mechanism similar to Java exceptions (throw/catch/unwind-protect) that is - among other things - used by condition system to actually unwind stack when it determines that is necessary. So you can probably abuse Java exceptions to implement condition system or - more generally - escape continuations.

Problem is that such mechanism has to be used consistently everywhere to be useful. And when you find yourself rewriting standard library, you could well use CL instead of Java.


Sure... It's an interesting comparison, and there are trade offs with both systems. I can't see one as being clearly superior to the other though.


Can you elaborate on what you view as the downsides of the Lisp approach?


Exceptional doesn't mean once in a blue moon; it just means "not the normal flow", for example, it could mean missing data, which could be handled by returning and testing for a null in Java. Python has more efficient exception handling, so missing data is often handled by an exception. Programmers are encouraged to use this idiom. It has the advantage that the return value space is not cluttered by a distinguished error value. If a function returns an integer, is the error value 0, -1, or 9999?


From casually looking at both CPython and JVM code I conclude that implementation of exception handling in Java and Python is almost same. So this is more a cultural difference than effectiveness issue.


In Java you can do stuff like, the JVM is going to bounds check you anyway, so why bother checking yourself if you're about to go off the end of an array? Just i++ and catch IndexException (or whatever it's called).


You can do that, but nobody writes Java code like this since it's more awkward and aesthetically offensive than explicit bounds checking.


There is more to consider with exception handling than just the performance of when the exception is thrown. There can also be performance penalties with respect to setting the stack up to be exception recoverable.

Also, there are some domains where the speed of recovery of an exceptional condition can be important.

You can't just use a tired old quote to hand-wave away optimization when it is important. You have no control over optimization of the language itself, so often it is important to consider performance aspects of a language when selecting a tool for a job.


An exception may not necessarily be an exceptional condition - e.g. Cannot parse integer, cannot open file might legitimately occur in code. In my APIs I usually try and make a very strong distinction between Exceptions and "Alternate Paths".

The thing is, many languages and APIs use the same mechanism for both - or at most offer checked and unchecked varieties.

For me, there is a big difference between an exception that the client can and can't remediate - e.g. "file not found" as opposed to "database down".


I thought the complaint about slowness was about the try clauses, which of course are paid for whether an exception occurs or not. Did I miss something?


Kent Pitman, a member of the X3J13 Common Lisp standards committee, wrote a terrific paper (http://www.nhplace.com/kent/Papers/Condition-Handling-2001.h...) on the subject of various condition handling systems, which includes examples of various error-handling systems.


Also interesting because it mentions PL/I as an antecedent to the Lisp condition handling. It says that PL/I had downward lexical scoping. I can't remember the details (I haven't looked at PL/I for over 20 years), but I believe that it means that the PL/I exception handler had access to the whole call stack, so you could make meaningful stack dumps from the handler, including variables. By today's standards the whole thing was quite clunky. A RETURN from the handler would resume execution. A GOTO to a label in the procedure enclosing the handler would unwind the call stack.


I don't know if the JVM does this right now, but you CAN detect useless stack building and simply not do it. In other words, the CPU cycle penalty of java's (and most other language's) exception mechanism can be virtually eliminated. The other benefits (resuming, for example) are a different issue.

The issue that I think the lisper of the OP is talking about is something like this:

try { something(); } catch ( SomeException e ) { //don't ever even look at e's stack, and do something else. }

Assuming the exception occurs, then to be safe the VM has to build the entire stack, which involves unwinding a lot of JIT work if you're unlucky, even though at the end it wasn't actually needed. However, assuming it matters that this is slow, which pretty much means that this is in a tight loop someplace, then a JIT compiler can detect that this situation always occurs, and simply avoid creating the stack.

There are still very interesting applications, such as logging and resuming, for not unwinding down the stack before giving the handlers in the chain an opportunity, but the CPU argument seems void to me.


Like just about everything in programming it seem to be a tradeoff.

In Java the try/catches are (a) implicitly "registered" on the call stack, and when an exception is thrown (b) then the runtime has to do a little analysis to determine what to do.

In Lisp, the handlers are (a) explicitly registered up front and when an "exception" is thrown (b) it just uses the most recently registered handler.

(a) is a much more common task than (b), so that's where you should optimize. But I don't know enough enough about Lisp (or Java) to know how these are implemented. If it's just pushing/popping on a stack then maybe it's just as fast.


Sorry, I am not convinced.

Java's exception handling may be slower, but exceptions should be rare: In the few cases exceptions are not rare, return an object instead.

The issue of loosing some variables is misleading. First, the heap is not rolled back, so many of the side effects are still available for inspection. Second, how often do you want one method to handle another method's variables, and if you do, is either method maintainable? Finally, if frame variables are important, then you can put them in the exception.

If you want to complain about loosing context, try PL-SQL: "Raising" an exception rolls back all evidence of there being a problem.


Talking about things being 'godawful slow' is lame without showing context and timings.


Caught 1000000 exceptions in 859 millis

See http://kip.sateh.com/ExceptionsAreSlow.txt


So why aren't Java exceptions resumable?


Because by the time a java exception is caught, the stack has already been unwound too far (because java must do so to find the nearest exception handler).

Lisp, basically, keeps track of exception handlers in a table seperate from the runtime stack. Hence, it doesn't need to unwind the stack to find an exception handler - it can just look it up without destroying the existing stack.


And I find this to be a complete pain in the ass. Seriously, you can't even print out variables to see what went wrong when you are building/debugging. They're no longer in scope.


What do you mean by "this" - Java's unwinding of the stack? Because in Lisp you just visit whatever stack frame you want and see everything that's in scope.

The real problem with Lisp environments (at least the open-source ones) is the lack of acceptable modern debuggers.


The fact that my "catch" block is in a different scope than the "try" block. If I really needed to do it, I could subclass "exception" and dump my variables there. But usually I'm just debugging something stupid so it's easier to put in "print" statements.

I've even put in a "macro" that gives me a "print" statement with a long string of "+" afterword so I can quickly spot them in my code and delete them (but don't tell anybody, 'cuz that's just plain wrong...).


What features does a "modern" debugger have, as opposed to non-modern ones.


Single-stepping, inspection of variables, view source code of current instruction. That's about it. I'm only calling it "modern" to distinguish it from what I'm using, which seems neanderthal.

I know that Slime is supposed to do these things if you use SCBL and various planets align, but (a) I've never gotten it to work, (b) it's obviously no one's priority, and (c) I don't use SBCL much anymore.


Try Smalltalk, it has both a modern IDE/Debugger with automatic refactoring support and resumable/restartable exceptions/notifications.


I think people who use lisp, use it because it's lisp and not smalltalk :-).


I think people who use Smalltalk, use it because it's Smalltalk and not Lisp :-).


Smalltalk does have some advantages over Lisp, including the ones you mention. But lack of a good debugger isn't enough to make me switch. I like Lisp a lot. Besides, switching to Smalltalk would be more work than writing (modernizing, really) a Lisp debugger.


Just steal ideas from the Smalltalk debuggers. You can see their source. If you make a copy of the debugger, you can probably even debug and step through the copy debugger in the debugger.


Yes, that is annoying. It also consumes a lot more memory to handle exceptions in the way Lisp does. That's neither good nor bad, just an observation.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: