News for hackers. Stuff that matters.
Re:Current hardware trends make C++ exceptions harder to jus... by ncmncm on Tuesday February 22, 2022 @11:39PM (#30437710)
This just spectacularly bad advice and policy. Point performance is the worst way to decide about error handling.

The overlap between "things that fail and the caller knows something useful to do", and "things that fail and somebody maybe six levels back in the call chain might know something to do" is remarkably thin. Most things are the latter. Once in a while, it makes sense to provide one for each: (1) open a file, and it had better succeed or you might have to bail out, or (2) see if a file is there.

The reason exception is the default, aside from that there are plenty of ways to return an error report, is that when you need code to clean up, the destructors are right there. They get exercised every time through the code, so are well-tested. How often is your special-snowflake error handler ever exercised? And the next one? And the next one? Even before you have given a moment's thought to error handling, your destructors are cleaning up, correctly. So, even if you haven't written code to handle it, your program has remained in a well-defined state. And, very often, you don't even need to write them: they are auto-generated from things the compiler already knows.

If you have set up a high-level catch block, you get there without fuss, and that code knows something correct to do, even if it is only to shut down cleanly. If you left a record of what was about to be attempted, that code knows what to try next.

Re:Current hardware trends make C++ exceptions harder to jus... by randyrand on Wednesday February 23, 2022 @12:08AM (#30437893)
> Point performance is the worst way to decide about error handling.

My point is exactly that. It's more important to have a single common way of doing error handling than it is to have a small perf improvement. That's why I recommend return values. You'll never hit a perf problem with them.

Exceptions cannot be used in code hot paths (~thousands of iterations) when it's not a small perf problem anymore. Therefore, they cannot be your only method for signaling failure. And therefore, assuming we care about consistency and composability of code, it's far better to use return value based error handling unless you have good reason (almost never). Ideally with syntatic sugar from the compiler (see Rust, Zig, Swift, etc).

> when you need code to clean up, the destructors are right there.

Exceptions are not needed to achieve this. C++ calls the same 'well tested' destructors when you return from a function.

The one point in your favor, that I acknowledged above, is that C++ requires exceptions to report failure from Constructors and Destructors, for OOP, std::vector initialization, for much of the standard library etc. Thats a travesty.

Re:Current hardware trends make C++ exceptions harder to jus... by ncmncm on Wednesday February 23, 2022 @01:38AM (#30438371)
Without exceptions, you will hit a performance problem on every single call, successful or not. That is what happens at Google, and in Rust: almost every last call site in the whole system has a "if" check. I.e., it is both badly obscured, and also slow.

You are arguing that programmers should not be obliged to make judicious choices. But that is exactly the job of every programmer. Dodging consequential choices practically defines a bad programmer.

Your assertion that destructors handle errors reported by return value is disingenuous, if not actively dishonest. The code checking for and acting on a failure is not in a destructor, and is never exercised except when you succeed in provoking exactly that failure.

You are writing bad code, and should be ashamed.

Re:Current hardware trends make C++ exceptions harder to jus... by jstimpfle on Wednesday February 23, 2022 @04:51AM (#30439463)
I can't imagine error-checking if's being a source of slowness. How are exceptions generated in the first place? Using if's. And anyway, even well structured code is full of if's (e.g. array iteration), and it's not a problem. Especially for error-checking, when the error case is rare, the CPU will correctly predict the branch to take most of the time, so the cost of the if is negligible.

The only call where if's should probably be minimized is on hot paths, where there shouldn't be any I/O or other exception code anyway.

I would agree that manual stack unwinding (many stack frames deep) by propating error values up the call chain is bad. The problem here is not the if's, but that the program structure is unnecessarily convoluted. This can happen when specific code is calling generic code a lot (which can be avoided by having the generic code call the specific code instead).

> You are writing bad code, and should be ashamed.

Woah, calm your horses!

Re:Current hardware trends make C++ exceptions harder to jus... by gumby on Wednesday February 23, 2022 @08:54PM (#30449362)
> I can't imagine error-checking if's being a source of slowness.

The overhead of doing this is measured in the article itself.

Re:Current hardware trends make C++ exceptions harder to jus... by jstimpfle on Thursday February 24, 2022 @12:33AM (#30450763)
As far as I can see, only the overhead of throwing exceptions is measured. Not error-checking if's (which might be quite hard to measure).
Re:Current hardware trends make C++ exceptions harder to jus... by ncmncm on Wednesday February 23, 2022 @06:22AM (#30439959)
That "if" that guards the throw is the same "if" as would guard the error return. It is the second "if", in the caller, checking the returned result, that up-to-doubles your branch-prediction cache footprint. On, yes, the hot path.

Program structure corruption is another problem. They add.

Bad code is a tax on all of us.

Re:Current hardware trends make C++ exceptions harder to jus... by jstimpfle on Wednesday February 23, 2022 @09:20AM (#30441198)
Which is what I said. And I still don't see how most code that potentially returns errors would be on a hot path. Code that is really on a hot path should probably not call into generic code anyway, and/or that called code should be inlined.

I don't know, as a C programmer, somehow I rarely run into these situations where I have to check error return values. I think the reason is that I work hard to avoid wrappers around wrappers around wrappers. Mostly error checking is required when interacting with the OS - i.e. for I/O, which is not critical paths.

A simple example would be memory allocation. A good C programmer allocates memory upfront. A bad C++ programmer could go, "eh, if it's so convenient I'll simply declare this std::vector locally, and if allocation fails it'll throw an exception and RAII and exceptions will solve the error handling issue magically without me having to type a single keystroke". Of course allocating each time is a lot slower than allocating only a single time upfront; no matter how much faster or convenient and individual allocation and error checking would be. This example shows how the perception of speed can often be warped because we're measuring the wrong thing entirely.

What is bad code is often not clear cut, and neither is how to improve it.

Re:Current hardware trends make C++ exceptions harder to jus... by ncmncm on Wednesday February 23, 2022 @02:40PM (#30445334)
Inlined code still has all the "if" checks of the original code, just without the actual "jsr" and "ret" instructions in between.

Comparing a good C coder to a bad C++ coder is meaningless: By definition, a bad coder makes bad choices, whatever the language.

Bad code is slow in a place where speed matters. It is improved by making it fast. Language choice does not affect this.

What language choice does affect is whether you can afford to spend the attention needed to make the code fast. C++ provides tools to offload busywork, freeing your attention to apply to what matters. It remains the programmer's job to choose that.

Re:Current hardware trends make C++ exceptions harder to jus... by jstimpfle on Wednesday February 23, 2022 @05:57PM (#30447801)
I'm not sure what you had in mind, but this little example is compiled using a single test: https://godbolt.org/z/bcqPWEn4a

I still can't imagine a situation where speed matters and where exceptional situations are likely and also hard to handle in a performant way with explicit error checking. Do you have any examples?

Re:Current hardware trends make C++ exceptions harder to jus... by ncmncm on Thursday February 24, 2022 @12:42AM (#30450863)
It suffices that exceptional situations are possible and must be checked for.

It is true that in C++, as in Rust, it is much more common than in C to have very short functions that call other very short functions that, all composed together, do a job that, absent abstraction, might all be coded in a single larger function tailored to the specific use case; and the compiler squeezes out most of the calls and generates object code as if for that single, longer function. Therefore, there are many more places where an error return report would need to bubble up through, and that would cripple performance to handle the naive way, without exceptions.

It might be that some languages that have a native "result" type, and automatically generate checking code at the call site, can squeeze out most of the intermediate-level checks when composing inline functions together, e.g. if actual errors mostly occur at leaf nodes of the call tree. That could mitigate the heavy overhead cost of the method. But I don't know if any compilers do such an optimization, or if they do, how well it really works. Coding an optimizer is hard.

Re:Current hardware trends make C++ exceptions harder to jus... by jstimpfle on Thursday February 24, 2022 @07:16AM (#30453814)
> Therefore, there are many more places where an error return report would need to bubble up through, and that would cripple performance to handle the naive way, without exceptions.

Do you have any evidence for these claims? If a called function is quite long by itself, then there is hardly any added overhead if the client checks the returned code and that check would be "redundant".

For example, if I make a syscall and check the return value, there will be a duplicated checking effort with the kernel code in a way - but compared to the cost of calling into the kernel that overhead is a negligible cost for the benefit of modularization (kernel vs userspace application). This overhead will be very close to unmeasureable, and does not justify the introduction of exceptions which are an additional mechanism to return values which introduce syntactic and binary incompatibilities (i.e. non-orthogonal functionality).

Oh, by the way - wouldn't exceptions and stack unwinding have to do just as much work per stack frame? It can't really just skip over all frames in a single instruction.

I think the best solution to cruft is generally to not have it in the first place - instead of introducing a mechanism to skip over layers upon layers of "abstraction", it's better to just not have these layers. There should not be a large call chain that unwraps multiple stack frames in a row. The best code is code that just does what needs to be done in a straightforward way without using any library cruft. Nothing to "optimize" away this way.

Give any concrete example where exceptions are useful in order to work around cruft, and we can see if there is a better way to write it that does not require exceptions.

Frankly it seems hilarious to me that we're having a discussion about optimizing a few if's because they allegedly add too much overhead for the work of skipping over layers of cruft.

In the past years I have seen the need to use longjmp() (which is in a way a mechanism to get exceptions for C) only one single time, when I was coding an interpreter that got embedded into a longer running application. I didn't know how to skip through recursive layers of parsing calls in the recursive descent parser. But I still feel like there is a better way to write the parser (like, pushing work to parse to a data structure instead representing it as a nested stack frame). This could also benefit error reporting for example.

Re:Current hardware trends make C++ exceptions harder to jus... by ncmncm on Thursday February 24, 2022 @06:57PM (#30462046)
What you are calling "layers of cruft", other people call abstraction. That abstraction enables people to have code exactly tailored for a specific use without writing it over and over, slightly changed each time, for each use.

In C, of course, you have no choice; you write the whole function over and over. C programs are, e.g., filled with custom one-off hash tables, because you can't write a performant, general hash table library in C. People using C++ and Rust do not code their own hash tables, because they cannot match the performance of well-tested and tuned library components.

As a consequence, modern C++ and Rust coders get better-than-C performance for a lot less work, and ship with many fewer bugs, by relying on mature, well-tuned libraries. That is worth a lot of what you call cruft.

That said, it is not uncommon to find C++ and Rust coders aping what they see in general-purpose libraries by adding superfluous cruft in their own programs, that provides no such benefit. But that is a complaint for another day.

Re:Current hardware trends make C++ exceptions harder to jus... by jstimpfle on Thursday February 24, 2022 @08:01PM (#30462511)
Somehow the discussion shifted away from the original topic...
Re:Current hardware trends make C++ exceptions harder to jus... by jstimpfle on Thursday February 24, 2022 @11:45AM (#30456903)
> wouldn't exceptions and stack unwinding have to do just as much work per stack frame? It can't really just skip over all frames in a single instruction.

Taking that back. I forgot for a second that we're focusing on the cases where the exception is not thrown.

Re:Current hardware trends make C++ exceptions harder to jus... by kllrnohj on Wednesday February 23, 2022 @07:09AM (#30440199)
> That's why I recommend return values. You'll never hit a perf problem with them.

> Exceptions cannot be used in code hot paths (~thousands of iterations) when it's not a small perf problem anymore.

You should read the paper in the link. Both of your claims here are exactly backward.

The problem is "just" that exceptions don't scale across multiple threads due to internally using a global lock. They are otherwise much faster than return values (4x faster in the trivial example)

Re:Current hardware trends make C++ exceptions harder to jus... by randyrand on Wednesday February 23, 2022 @04:12PM (#30446513)
Are you sure? Single threaded

  18-16ms (boost::LEAF) vs 19-23ms (exceptions)
A better comparison is found here:

https://lordsoftech.com/programming/error-codes-are-far-slow...

> TL;DR On modern 64-bit PC architectures, C++ exceptions only add unreachable code with destructor calls into functions and their effect on performance is below 1%, but such low values are difficult to measure. Handling rare errors with return values requires additional branching that slows down the program in realistic scenarios by about 5% and are also less convenient. If an exception is actually thrown, stack unwinding costs about 2 µs per stack frame.

5% cost to using error codes. In exchange for that 5% you don't fall off a performance cliff if you hit a bunch of errors unexpectedly, and can confidently use them in hot paths and on CPUs with multiple cores.

I'll take the lower perf complexity and greater perf robustness of error codes any day. Reliability and predictability is more important to me than a 5% perf cost.

Re:Current hardware trends make C++ exceptions harder to jus... by kllrnohj on Thursday February 24, 2022 @11:34AM (#30456698)
Your "better comparison" finds that error codes are slower than exceptions? It's right there at the top in bold even if you don't want to read the whole thing:

> TL;DR On modern 64-bit PC architectures, C++ exceptions only add unreachable code with destructor calls into functions and their effect on performance is below 1%, but such low values are difficult to measure. Handling rare errors with return values requires additional branching that slows down the program in realistic scenarios by about 5% and are also less convenient.

Also I see you "cherry picked" your counter example using the sqrt test, which is less influenced by per-call overhead. Using instead the fib test, which is basically just a test of call overhead, we get instead:

   12ms-14ms (exceptions) vs. 22-23ms (boost::LEAF)
Both the paper & your link are in agreement - exceptions are faster than error return values.

> 5% cost to using error codes. In exchange for that 5% you don't fall off a performance cliff if you hit a bunch of errors unexpectedly, and can confidently use them in hot paths and on CPUs with multiple cores.

You also get more fragility since error handling is now manual. I'd rather have performance and reliability, which only exceptions can provide. They don't provide that today thanks to the global mutex, but the point of the paper & related explorations is that that both can be fixed, and that fixing it provides a superior performing solution. And a more reliable one.

Unless you're arguing that it's better to just always have a 5% performance tax & manual, error-prone error handling? We shouldn't attempt to fix the toolchain at all? The paper isn't arguing that people should use exceptions today. The paper is arguing that exceptions should be fixed so that they can be used in the future.

Re:Current hardware trends make C++ exceptions harder to jus... by randyrand on Monday February 28, 2022 @12:46PM (#30502657)
> Unless you're arguing that it's better to just always have a 5% performance tax & manual, error-prone error handling?

Exceptions today are untenable for a library author because you don't know which input data your callers will use, and therefore can't predict how often errors will be thrown.

Ideally we'd fix c++ with better manual propagation of errors and compiler warnings, like Rust or Zig.

Even if the global mutex is fixed, the single threaded perf numbers are terrible if you hit a bunch of unexpected errors. That seems unfixable.

I'd rather have 5% slower code, than 5% faster code with occasional 1000% slow downs (in the worst case).

Re:Current hardware trends make C++ exceptions harder to jus... by kllrnohj on Monday February 28, 2022 @05:24PM (#30505998)
> I'd rather have 5% slower code, than 5% faster code with occasional 1000% slow downs (in the worst case).

Perhaps, but that's a strawman through & through. C++ exceptions don't have that performance cliff when thrown, per both the paper in the OP & in your link. They are fairly cheap to throw assuming a single-threaded application.

That is, the fib example with 10% failure rate has the same performance using exceptions & using Boost::LEAF.

Unless you're calling it "unfixable" because there isn't actually anything to fix in the first place...

Re:Current hardware trends make C++ exceptions harder to jus... by ncmncm on Wednesday February 23, 2022 @02:45PM (#30445413)
Yes. The article author's mistake was actually throwing those exceptions too often. For arithmetic errors, NaN is usually the better choice.
Re:Current hardware trends make C++ exceptions harder to jus... by kllrnohj on Wednesday February 23, 2022 @04:01PM (#30446388)
It's an exploration in scaling, how could that possibly have been a "mistake"? The paper isn't advocating for throwing an exception instead of returning NaN in the specific case of calculating a square root. It's instead only exploring the overhead & scaling of various error handling types.