Live data from Hacker News

Current hardware trends make C++ exceptions harder to justify

open-std.org

211–220 of 516 posts

Re: Current hardware trends make C++ exceptions harder to justify

#211
post #9
post #3

> Root cause > Traditional C++ exceptions have two main problems: > 1) the exceptions are allocated in dynamic memory because of inheritance and because of non-local constructs like std::current_exception. This prevents basic optimizations like transforming a throw into a goto, because other parts of the program should be able to see that dynamically allocated exception object. And it causes problems with throwing ex…

You don't have to start over with the whole language. Just use -fno-exceptions in your project and dictate the use of std::optional, or absl::StatusOr, or whatever your favorite variant return type may be. For the examples in the article, it may be perfectly fine to not support failure, to simply std::abort whenever the sqrt of a non-positive is requested and rename the function sqrt_or_die.

You also have to abandon operator new and STL unless you want to pretend they never fail.

Re: Current hardware trends make C++ exceptions harder to justify

#212
post #170
post #129

Earlier quoted context omitted.

Regarding maintainability ("hard-to-follow"), I’ve become a big fan of Java’s checked exceptions (and also their causal chaining and adding of "suppressed" exceptions, which would be nice to have for C++ destructors). I effectively see them as a sum type together with the return type, just using different syntax. It’s an important reason why I stick to the language, because no other language has that kind of statical…

Java's checked exceptions are generally regarded as a mistake. There's a reason no other languages has them, and newer JVM languages (Groovy, Clojure, Scala, Kotlin) treat all exceptions as runtime. Anders Hejlsberg (creator of Delphi, C# and Typescript) also has an excellent article on their problems [1]. In modern Java I see nearly only runtime exceptions used, especially because that's necessary for most Java 8+ l…

Most of the software I write is designed to be fault-tolerant, and checked exceptions are fantastic way of detecting potential faults. The problem is that checking is baked into the exception definition instead of its usage.

If I declare "throws NullPointerException", then this should mean I want it to be a checked exception. This should force the caller to catch the exception, or declare throwing it, or to simply throw it out again without declaring it. This would effectively convert the checked exception into an unchecked exception.

Converting a checked exception to an unchecked exception is possible in Java, and I do it whenever it makes the most sense instead of wrapping the exception, which is just a mess. Unfortunately, there's no way to make an unchecked exception behave as if it was a checked exception. It's easier to reduce fault tolerant checks then to add more in.

Some might argue that converting an exception to be unchecked is a bad idea, but this sort of thing is done all the time in JVM languages that are designed to interoperate with Java. If I call a Scala library from Java, and that library is doing I/O, then IOException has now effectively become unchecked.

Re: Current hardware trends make C++ exceptions harder to justify

#213
post #191

I find this paper quite unconvincing. Exceptions are exceptional so in principle it doesn't matter (within reason) how long it takes to throw one as long as it costs nothing not to do so. So measuring the cost of repeated throws IMHO doesn't cast light on any useful case, and the approaches that add runtime cost for the path not taken, even Herb Sutter's, are not acceptable. His code transformation example is simply…

But there's a design pattern where nearly everything is returned as an exception. It's the normal path in some code.

I deplore that - its side-effects writ large. But those that use it, find it reasonable and sensible.

It's become hard to distinguish right and wrong when it comes to CPU optimization. Different versions of the 'same' CPU can have wildly different sweet spots.

Re: Current hardware trends make C++ exceptions harder to justify

#214
post #170

Earlier quoted context omitted.

Java's checked exceptions are generally regarded as a mistake. There's a reason no other languages has them, and newer JVM languages (Groovy, Clojure, Scala, Kotlin) treat all exceptions as runtime. Anders Hejlsberg (creator of Delphi, C# and Typescript) also has an excellent article on their problems [1]. In modern Java I see nearly only runtime exceptions used, especially because that's necessary for most Java 8+ l…

" Java's checked exceptions are generally regarded as a mistake. " By people who do not want to believe that errors are part of a system's API and prefer to just write the happy path and let any exception kill the process. And who don't mind getting called at 2:00 am because a dependency buried deep in a subsystem threw an exception that you'd never heard of before.

> By people who do not want to believe that errors are part of a system's API…

The point was that you should be returning errors, not throwing them. Runtime exceptions (null reference, division by zero, out of memory, etc.) ought to indicate a fatal error in the (sub)program or runtime environment. You can trap these, and report them, but it's usually a mistake to try to case-match on them. Unlike errors, which are predictable, enumerable elements of the design, runtime exceptions should be treated as an open set.

Re: Current hardware trends make C++ exceptions harder to justify

#215

This paper confirmed what I suspected for some time: exceptions are still the only basically-zero-overhead solution available, which ironically completely justifies their existence in usage patterns where exceptions are actually exceptional. There are times when writing/profiling Rust when I wish I had access to exceptions instead of `Result` propagation. I do agree with the mentioned design issues though.

> There are times when writing/profiling Rust when I wish I had access to exceptions instead of `Result` propagation.

This introduces a whole bunch of design issues, but isn't `panic` (in unwinding mode) basically C++'s exceptions, implementation-wise? Couldn't we use `panic` + `catch_unwind` as a poor man's exception system, should the performance situation really require so?

Also, could we maybe add an attribute `#[exceptional]` to enum variants in a match, that would result in a match implementation closer to exceptions, implementation wise?

Re: Current hardware trends make C++ exceptions harder to justify

#216

Earlier quoted context omitted.

Yeah I knew I was going to get called out with a Rust comparison. I think the Rust approach basically acknowledges the issue with what C++ did— that automatic initialization is cute but ultimately wasn't worth what it ended up costing in terms of hidden control flow, poor error handling, static initialization issues, etc. Anyway, Rust basically deals with it by giving the class designer the choice to supply factory f…

It's also easy to provide a default factory: https://doc.rust-lang.org/std/default/trait.Default.html

True, but the naming does matter. Calling Thing::default() clearly communicates that your just getting baseline values and not a lot of magical other initialization stuff going on— with a Thing::Thing() in C++, you're really at the mercy of whatever the project conventions are for how "fat" the constructor is going to be.

I think the naming is also important for cases where there are potentially multiple reasonable defaults, even something as basic as the difference between Vector3::Vector3() and Vector3::zero().

Re: Current hardware trends make C++ exceptions harder to justify

#217

Earlier quoted context omitted.

If you run into contention issues have you tried scaling horizontally? Running on 128 single core VMs should mitigate what you see.

That would turn communicating between threads into communicating between VMs, which is a disastrous change in itself.

how do you know the threads need to communicate with each other?

Re: Current hardware trends make C++ exceptions harder to justify

#218
post #192

Earlier quoted context omitted.

> Not sure if the Rust implementation has a different design to make it perform better though. Prolly not, I expect the issue comes from the increase in branches since a value-based error reporting has to branch on every function return. Even if the branch is predictible, it’s not free. And fib() would be a worst-case scenario as it does very little per-call, the constant per-call overhead would be rather major.

Using up scarce branch-prediction slots is a good way to make your program unoptimizable. Time wasted because you ran out will not show up anywhere localized on your profile. (Likewise blowing any other cache.)

Using up BTB slots is an interesting problem but in practice doesn't seem to be a big issue. If it was, ISAs would use things like hinted branches but instead they've been taking them away. Code size is more important but hot/cold splitting can help there.

A problem with using exceptions instead is they defeat the return address prediction by unwinding the stack.

Re: Current hardware trends make C++ exceptions harder to justify

#219
post #26

> As illustrational example consider this small code fragment: Consider this crap small code fragment. Any function that returns void is almost certainly wrong.

Good thing printf returns something, otherwise it might wrong or useless!

Re: Current hardware trends make C++ exceptions harder to justify

#220
post #210

Earlier quoted context omitted.

How exceptional exceptions really are depends on the code being run. The serialization of stack unwinding issue is a real serious problem.

> How exceptional exceptions really are depends on the code being run. Well yes, but EH is basically trying to be the best way to do a highly non-local goto with a dynamic target. If that part is your bottleneck than indeed you may have a more specialized requirement. I do on occasion build a homemade version of a standard library datatype because I have a specialized need and only need implement the subset our code…

I agree. I'm not sure breaking the ABI to fix the unwinder serialization issue should be a show-stopper, considering that there still isn't a C++ ABI.
Post reply on HN