Why Java exceptions are slow (and Common Lisp conditions aren't)
groups.google.com
groups.google.com
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.
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.
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.
Exception.OnThrown += LogThrownException;
Where this callback has some signature which can examine the stack, locals, and potentially resume?
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.
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.
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".
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.
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.
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.
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.
The real problem with Lisp environments (at least the open-source ones) is the lack of acceptable modern debuggers.
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...).
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.