6 ms·
Why Java exceptions are slow (and Common Lisp conditions aren't)
- stcredzero 18y agoSo why aren't Java exceptions resumable?
- smanek 18y agoBecause 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.
- dhimes 18y agoAnd 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.
- arturoman 18y agoYes, 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.
- gruseom 18y agoWhat 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.
- gnaritas 18y agoTry Smalltalk, it has both a modern IDE/Debugger with automatic refactoring support and resumable/restartable exceptions/notifications.
- gruseom 18y agoSmalltalk 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.
- stcredzero 18y agoJust 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.
- catch23 18y agoI think people who use lisp, use it because it's lisp and not smalltalk :-).
- gnaritas 18y agoI think people who use Smalltalk, use it because it's Smalltalk and not Lisp :-).
- Prrometheus 18y agoWhat features does a "modern" debugger have, as opposed to non-modern ones.
- gruseom 18y agoSingle-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.
- deleted 18y ago[deleted]
- gcv 18y agoKent Pitman, a member of the X3J13 Common Lisp standards committee, wrote a terrific paper (http://www.nhplace.com/kent/Papers/Condition-Handling-2001.html 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.
- russell 18y agoAlso 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.
- axod 18y agoAn 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.
- arturoman 18y agoThere 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.
- mechanical_fish 18y agoThe 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.
- axod 18y agoSure... 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.
- barrkel 18y agoFWIW, 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.
- rzwitserloot 18y agoI 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.
- tlrobinson 18y agoLike 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.
- klahnako 18y agoSorry, 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.
- st3fan 18y agoTalking about things being 'godawful slow' is lame without showing context and timings.
- st3fan 18y agoCaught 1000000 exceptions in 859 millis See http://kip.sateh.com/ExceptionsAreSlow.txt http://kip.sateh.com/ExceptionsAreSlow.txt