Live data from Hacker News

C++ Exceptions: Under the Hood (2013)

monkeywritescode.blogspot.com

111–116 of 116 posts

Re: C++ Exceptions: Under the Hood (2013)

#111

Working with and implementing C++ exceptions for 30 years now, including implementing exception handling for Windows, DOS extenders, and Posix (all very different), and then re-implementing them for D, I have sadly come to the conclusion that exceptions are a giant mistake. 1. they are very hard to understand all the way down 2. they are largely undocumented in how they're implemented 3. they are slow when thrown 4.…

I'd suggest to use Lisp, which is a under-rated yet powerful language.

[Why?] https://gigamonkeys.com/book/introduction-why-lisp.html

[Exceptions] https://gigamonkeys.com/book/beyond-exception-handling-condi...

Re: C++ Exceptions: Under the Hood (2013)

#112
post #39

Earlier quoted context omitted.

I would go with the Erlang approach. Just die FFS. Let the process monitor restart you if you deserve to live.

That only makes sense if units of your code run in a loop and communicate asynchronously :). But, if you want a simple supervisor pattern in C++, then... try/catch block is your supervisor, exceptions are how your process dies. Consider: template auto CallWithSupervision(Fn fn) -> decltype(fn()) { // supervision loop // configure conditions as needed while(true) { try { return fn(); } catch(std::exception& e) { // lo…

Except it isn't robust.

Exceptions occur when "Very Bad No Good Undefined Horrible Things" have (at last, been found to have) happened.

So if you have no guarantee that the memory space is uncorrupted, you have no guarantee that all resources have been recovered, you have no guarantee that in attempting to deallocated resources that they have been allocated in the first place. (Read the fine fine print on exceptions in constructors and destructors.) TL;DR; Don't do that. But exceptions are what happen when people do what they shouldn't.

Re: C++ Exceptions: Under the Hood (2013)

#113
post #76

Earlier quoted context omitted.

Enregistering of variables is also lost, because to restore them during unwinding they have to be retrieved from the stack.

Not sure what you mean here, but generally Java JITs generally don't use callee-saved registers at all because they need precise stack maps for GC. So whatever small amounts performance they might lose here isn't due to exceptions.

OpenJDK's (HotSpot) stack maps do support callee-saved registers -- and they are used in some special cases, like safepoint stubs (that spill all registers at a poll-point if a safepoint is triggered) -- but you're correct that they've been removed from ordinary Java calls altogether on all platforms, now.

Re: C++ Exceptions: Under the Hood (2013)

#114
post #76

Earlier quoted context omitted.

Not sure what you mean here, but generally Java JITs generally don't use callee-saved registers at all because they need precise stack maps for GC. So whatever small amounts performance they might lose here isn't due to exceptions.

> Not sure what you mean here Allocating local variables into registers rather than assigning stack locations for them. Registers are faster than memory. EH unwinders restore the stack before jumping to the catch block, but not the register contents. Stack maps wouldn't be necessary for non-pointers, like an integer variable. Stack maps also have their own performance problems, which is why D doesn't use them.

Inside a single physical frame, that contains many inlined Java methods (the current default is inlining up to depth of 15, not counting "trivial" methods that are always inlined), locals are always in registers (unless they're exhausted), and are spilled only at safepoints, e.g. when another physical call is made, which is where all languages have to spill, too. Stack maps include only pointers and incur no runtime overhead on the fast-path, and are not used when throwing exceptions, even outside the current physical frame. There's additional debug info metadata associated with compiled code, which also incurs no runtime overhead on fast paths, that maps even primitives to their registers/spill locations; that debug info also maps compiled code locations to their logical, VM bytecode, source ("de-inlining"), and is consulted in the creation of the exception when a stack trace is requested.

Re: C++ Exceptions: Under the Hood (2013)

#115
post #56

Earlier quoted context omitted.

I agree that a modern high-level programming language model needs an ergonomic error model. But C++ exceptions are not the only way to go. You can have error model that have similar (or even better ergonomy) than C++ while not having any of the drawbacks (like extremely complicated runtime stack, slow exception handling, messed up control flow etc.). Basically, in my personal opinion, any error handling that involves…

Note you didn't really answer my question at all. Right now lots of algorithms like std::search, std::find_if, etc. are not only exception-safe, but in fact exception-agnostic. Neither the algorithm, nor you , need to know a priori if your predicates will throw exceptions (which are things that may be literally impossible to know upfront), and yet despite that, (a) the algorithms will work completely correctly if any…

> Neither the algorithm, nor you, need to know a priori if your predicates will throw exceptions (which are things that may be literally impossible to know upfront)

I don't think this property is desirable at all. I prefer to know whether a function can or cannot result in an error, ideally encoded within the type system. The C++ "everything can throw" paradigm yo describe here obfuscates the program logic and promotes bad coding practices. I know, C++ programers like to argue that "everything throws" is a natural property of any real world code, but somehow folks are able to work with Rust and Swift without too much hassle.

> If you're really saying you can find a strictly better solution, then we're all definitely interested in hearing... and I'll believe it when I see it.

A strictly better solution has been found long time: error sum types.

> Performance is also a big one, and C++ is designed for maximizing performance in non-exceptional executions. I don't know what error models you're thinking of, but anything along the obvious stuff I've seen (like the usual "replace T with maybe/optional/fancy") would come with far greater performance hits n the 'happy' paths than C++ has (not to mention potential increases in memory usage, etc. in more complex cases)

This is again a very popular argument I've seen used by many in the C++ community, but the simple fact is that this argument is simply not true. Already very naive result type implementations using C++ show no measurable performance difference in the "good" path (with a non-trivial function), and using an optimized calling convention makes error sum types zero-cost on modern hardware.

For example, Swift uses a dedicated register to signal exceptional function result. On the "good" path, you have to zero this register in the callee and conditionally jump on its value in the caller. These operations are essentially free on any modern CPU with superscalar execution, register renaming and branch prediction. The only cost is a register and a few extra instructions which won't carry any performance impact. One can optimize this even further by using condition flags to signal exceptional result (frees up a register and saves an instruction).

To sum it up, using result types with optimized calling conventions gives you the same performance as the C++ exceptions on the good path, much better performance on the exception path, saves space (few bytes of extra instructions take much less space than the unwind information), radically simplifies the compiler (no long jumps, functions enter and exit regularly), radically simplifies cleanup (function exits regularly and can run destructors as usual), simplifies the control flow and so on.

In fact, the only disadvantage I see with this implementation is that exception propagation might be slower than a longjump if you have hundreds of nested functions. But I think you have much bigger problems if you call stack looks like that...

Re: C++ Exceptions: Under the Hood (2013)

#116

Working with and implementing C++ exceptions for 30 years now, including implementing exception handling for Windows, DOS extenders, and Posix (all very different), and then re-implementing them for D, I have sadly come to the conclusion that exceptions are a giant mistake. 1. they are very hard to understand all the way down 2. they are largely undocumented in how they're implemented 3. they are slow when thrown 4.…

That's funny: I thought you were a proponent of exceptions because I read (a lot of years ago) a mail from you which said "who is going to check that printf failed"?

And this remain true: a lot of things can fail (arithmetic operations, every IO, etc) so the error system must be very "lean" otherwise the "happy path" is drowned in the error propagatio/handling code..

Post reply on HN