Live data from Hacker News

C++ Exceptions: Under the Hood (2013)

monkeywritescode.blogspot.com

31–40 of 116 posts

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

#31
post #24

Earlier quoted context omitted.

Java JITs are considerably more advanced than this. It's expensive to put in all the additional control flow edges to model exceptions, but once done, control and data flow analyses just work, as well as loop optimizations, code motion, inlining, register allocation--all of it "just works" (TM). Then you have to spit out a metric crapton of metadata to allow searching for the correct handler at runtime, but that's th…

Regarding putting in all the additional control flow edges: more edges mean fewer optimizations. That's not zero cost.

I actually agree that they technically aren't zero cost (notice I didn't even write that), but the cost is indirect. I've worked on a number of Java JITs and in practice, not a lot of hot code has catch blocks, and even when so, inlining is typically so deep that lots of exception edges (e.g. arising because a possible NPE) get optimized away.

Most of the lost optimization opportunities are second-order costs, not first-order costs. Java JITs make up for the extra flow edges by focusing more on global optimizations rather than local (e.g. GVN vs LVN, global code motion, global flow-sensitive load/store elimination), etc. Generally a possible exception edge splitting a basic block doesn't hurt because the non-exceptional control flow will still benefit from flow-sensitive optimizations (i.e. it has only one predecessor anyway).

We're splitting hairs anyway. Like I said, Java JITs are significantly more advanced at optimizing exception-heavy code. I'd be really surprised if you saw anything more than a 1% increase in performance, actually, no, scratch that. I doubt you can even reliably any speedup distinguishable from noise from just disabling all support for exceptions in most Java code, unless you are talking about metadata. Top-tier JITs really are that good.

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

#32
post #15

Earlier quoted context omitted.

What do you think of languages that use sum types for error handling, but can still unwind in a few scenarios? Reasonnable compromise, or should we get rid of all unwinding always? And if so, do we abort() or do we ask users to handle any and all possible errors.

Rust is one such language. I wrote a bit about exception safety in Rust here: https://users.rust-lang.org/t/c-pitfalls-hard-to-avoid-that-... In short, while the problem is mitigated somewhat compared to C++, it's still one of the most common causes of bugs in unsafe Rust code. Rust programs can choose to abort on all panics, rather than unwind. Firefox does this, for example.

We do this on all our Rust code as well (1.1 million LOC at this point).

While we can benefit from #[no_std] crates on crates.io, unfortunately we can't use any crates that require standard because the standard library does not propagate errors properly, so we maintain our own implementation of most of the standard library for Linux only, that propagates all errors using Result..

It's a huge pain point, but at least Rust allows us to do it.

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

#33
post #15

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.…

What do you think of languages that use sum types for error handling, but can still unwind in a few scenarios? Reasonnable compromise, or should we get rid of all unwinding always? And if so, do we abort() or do we ask users to handle any and all possible errors.

Now that I've been using Zig's error handling language primitives for some time, I've come to realize what the paradigm really is: a way to encode a "forwards" and a "backwards" at the same time, for the same block of code.

The usual way control flow progresses is forwards, but when an error occurs, it goes backwards, over the defers and errdefers.

C++ exception handling and other languages with destructors force you to do this declaratively, but then don't give enough control over exactly the situations they matter in: setup and teardown.

Meanwhile with explicit control, you just encode exactly what happens in the "backwards" control flow. No surprises, no trying to figure out what happens based on declarative rules. Once I figured this out, I was able to use it to simplify the logic of some things in the self-hosted compiler that are extremely error prone in the C++ implementation:

* Lazy source locations: passing in "none" for a source location, and then handling the "error.SourceLocationNeeded" and then doing the expensive calculations to find the source locations before retrying the operation.

* Generic instantiations: returning "error.GenericPoison" for when a type parameter cannot be determined without information from the callsite. In this case, the analysis is cleanly aborted and function marked as generic.

I'm pleased with how this turned out, and I've started to think of other languages in terms of how they map to this "forwards" and "backwards" control flow concept.

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

#34
post #31

Earlier quoted context omitted.

Regarding putting in all the additional control flow edges: more edges mean fewer optimizations. That's not zero cost.

I actually agree that they technically aren't zero cost (notice I didn't even write that), but the cost is indirect. I've worked on a number of Java JITs and in practice, not a lot of hot code has catch blocks, and even when so, inlining is typically so deep that lots of exception edges (e.g. arising because a possible NPE) get optimized away. Most of the lost optimization opportunities are second-order costs, not fi…

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

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

#35

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.…

Forcing the programmer to manually write stack unwinding code is not a solution.

That's like saying "garbage collecton is slow and complex, just use malloc() and free() instead".

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

#36
post #6
post #4

Earlier quoted context omitted.

Doesn't GCC support multiple exception handling options?

I'm not sure what your point is - mine was that the original post is specifically about GCC, not Standard C++.

“Standard C++” just specifies `throw` and `catch` (plus what happens when you traverse a block, nothrow declaration etc)

Every implementation has to do something to actually be standard C, and this is an example. It’s rather similar on other hardware, but as far as the standard goes that is irrelevant.

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

#37
post #15

Earlier quoted context omitted.

What do you think of languages that use sum types for error handling, but can still unwind in a few scenarios? Reasonnable compromise, or should we get rid of all unwinding always? And if so, do we abort() or do we ask users to handle any and all possible errors.

I've read the proposals for it. It certainly looks good, yet exception handling looked good 30 years ago, too. I haven't used sum types myself, and often it takes years to discern whether things are really good ideas or not. What I personally use is the "poisoning" technique. This involves marking an object as being in an error state, much like a floating point value can be in a NaN state. Any operation on a poisoned…

So that sounds like the way invalid floating point operations give NaN, and then the NaN propagates everywhere. I've always found this super annoying because its often hard to figure out where the NaN comes from. Does your solution differ from this in a way that's less annoying?

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

#38

Earlier quoted context omitted.

I've read the proposals for it. It certainly looks good, yet exception handling looked good 30 years ago, too. I haven't used sum types myself, and often it takes years to discern whether things are really good ideas or not. What I personally use is the "poisoning" technique. This involves marking an object as being in an error state, much like a floating point value can be in a NaN state. Any operation on a poisoned…

So that sounds like the way invalid floating point operations give NaN, and then the NaN propagates everywhere. I've always found this super annoying because its often hard to figure out where the NaN comes from. Does your solution differ from this in a way that's less annoying?

The FPU does not include the source in the NaN, but that doesn't mean your own objects can't.

What I do is have the error reported at the source, and then return the poisoned object. A better way would possibly be put the error message in the poisoned object, and report the error somewhere up the call stack.

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

#39
post #26

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.…

Can you describe your ideal error handling mechanisms? Or at least other mechanisms that feel more correct?

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

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

#40

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.…

Don't you need exceptions though? How do you terminate arbitrary operations without exceptions?

Like say you call an algorithm (like std::sort) and during a callback (e.g. in the comparator) you decide to cancel the operation (perhaps user-requested). With exceptions it's easy; you just throw an exception and then catch it. No need to touch or even know the intermediate callers. But without exceptions what do you do? You have to go modify or reimplement the source code of every intermediate function, which is a giant waste of effort at best, and in reality a likely vector for introducing code duplication, brittleness, and bugs.

Post reply on HN