I think this paper started with a conclusion, and worked backwards to justify it.
I think this paper started with a conclusion, and worked backwards to justify it.
Just think about this: If you have a 100 cores, and 1% of your tasks fail, one core is constantly unwinding. And due to the global lock you quickly get a queue of single threaded unwinding tasks. And things become worse, we expect to have machine with 256 cores soon, and there it is even more dangerous to throw.
If you do not believe me look here: http://wg21.link/p0709 It lists quite a few applications that explicitly forbid exceptions due to performance concerns.
That's a lot of failures. I'd question whether or not you should be using exceptions for something that happens 1% of the time all the time.
Admittedly you might not have any choice in the matter if it's a dependency that is failing.
Are you encoding a single file on a local machine? Or 1000 files on some HPC 128-core machine?
Do we really want to be in a world where all libraries need to be written with and without exceptions since we can't know the context they'll be used in?
So no, errors/exceptions are not rare depending on the context. So now do you use exceptions and accept that it doesn't scale across core counts? Or do you use return values and accept that it just makes all your code slower in all cases?
1% error rate sounds like a lot. But really how many HTTP errors do web servers return on average? Or other such systems? It's definitely more than 0%, and those services also scale nicely across multiple cores, so... Now we're just bikeshedding over the example numbers, not the core issue.
I've done it exactly once and I ended up removing it because it makes debugging exceptions that you care about really annoying.
Exceptions are at least a very central part of the normal flow control of parser combinator libraries.
However, I do not agree with the conclusion - what is the difference between removing exceptions and simply not using them in your project? All compilers already let you compile code without exceptions and the product I work on compiles all numerical code that way. I don't see what benefit you expect to reap from changing the fundamental way C++ exceptions are implemented and work. At the high end, you want a custom error solution fit for your particular task, I don't think we can have a generic exception framework that will work for every high-performance computing project.
I would suggest you need to address that 1% of failing tasks and determine what the issue is, as frankly, you are solving the wrong problem. If I might suggest a solution, it sounds like you are using threading when process farms might be a better solution.
(and if i'm way off the mark with my suggestion, apologies, i'm trying to help and have little information to work with).
as a user of audio software, I would really prefer them to have the occasional glitch instead of crashing, potentially loosing work. Like, how it is even a discussion ?
Surely you agree that it's better to have this throw an exception, and a big try.. catch around your audio callback so that just this tick, which loads the preset, fails, and then the program continues normally at the next tick, just with the wrong preset for a node, rather than calling abort() which entirely kills the program and looses what the user was working on ?
Yes I do agree but my point is that if any exception is being thrown for the sake of being handled then your Audio code is bad.
Generally your Audio loop should encounter no errors at all (it should be a pure process) so what the OP is saying is that excepting/aborting is fine in that case because it would truly be exceptional. I can’t imagine what the exceptional situation would be but to the extent one exists, I agree with that. My only point is that throwing an exception when there is no handler is no better than calling abort(), obviating the need for enabling exceptions.
thank god ! then we both agree.
> Generally your Audio loop should encounter no errors at all (it should be a pure process)
and audio handlers should never lock mutexes yet I have seen my fair share of them in the wild.
> My only point is that throwing an exception when there is no handler is no better than calling abort()
every thread or event loop should always have a catch-all handler at top level.
They are incorrect. Only bounded-time synchronization primitives can be used. E.g. lock-free queues.
> every thread or event loop should always have a catch-all handler at top level.
Two things. 1) There should be no reason for a properly coded real time audio loop to have a top level handler because exceptions should not be thrown because they cannot be guaranteed to be handled within the necessary time constraints.
2) top level exception handlers divorced from context cannot be handled, only ignored. Ignoring exceptions is very dangerous because there is generally no way for the top level handler to be sure that the rest of the program is in a correct state (unless it knows all code is exception-safe, which is rare). Continuing to run can result in data corruption. Exceptions leaving the context that has the information to handle them is a programming error, just like a failed assertion.
yes, my point is that incorrect code exists in the wild, that's the world we have to live in.
> properly coded real time audio loop
most things aren't properly coded, and yet still useful
Now, in this thread, exceptions are supposed to be used rarely. I don't see the difference between an exception and Rust/Go's `panic`. If the error is rare enough, then chances are you cannot gracefully recover. If the error isn't rare; then why are you using exceptions for control flow?
It's interesting that the lack of enforced exception handling was seen as a strength when exceptions were added to the language, compared to now when the lack of enforced exception handling is sometimes seen as a weakness. Programming language design and expectations are a moving target, so it'll be interesting to see how these things develop over time.
And yes, Rust and Go take a different approach. Is there right and wrong? No, i'd say just different approaches which will play to the strengths of some problem domains and coding styles.
that does not match my experience. Generally, rolling back to the state at the beginning of whatever user interaction caused the exception is good enough in a large amount of cases and a much better experience for end-users than abort()
This article makes it very clear that exceptions complicate analysis of performance. The non-local nature of exceptions mean I can't analyze performance in isolation. E.g. I can test that thread 1 always meets its deadlines (even with exceptions being thrown), then I can test that thread 2 always meets its deadlines.... but if thread 1 and thread 2 happen to throw exceptions at nearly the some time I might miss both deadlines. Who knows, maybe this means a thruster fires too long and that insertion burn fails ... and Mars has a new crater instead of a lander.
And, once you throw -fno-exceptions you are no longer using standard C++, which the standard library assumes. So, using anything that would throw exceptions on memory allocation failures is a no-go. You can work around this with extensive use of allocators (that reference enough static memory to avoid any possible out-of-memory situation)... but this is not looking like idiomatic C++ anymore, and most off-the-shelf libraries are unusable.
A completely local exceptions implementation (e.g. Herbceptions) would solve this.
Major subsystems that use no memory except what is passed in from above is also idiomatic C++.
C++ is a big tent. Things you do routinely in one part of a program, such as at startup, may be very different from what you do in a main loop, or in termination cleanup. Things my program does may be very different from what your program does.
for a more-or-less niche part of "real world". The overwhelming majority of desktop GUI apps rely on some C++ system - Qt, gtkmm, Wx, Blink, Gecko, FLTK, etc etc... and it is not an issue for those, for what exceptions are commonly used for (a write failing because the user disconnected the USB drive while it was copying, a system resource limit exhausted.. things like that). As much as massive parallel data processing tasks matter, I'd really prefer my language to not side-step writing end-user apps for something that happens at $bigcompany or $bigresearchlab.
here's the list of binaries that link against libstdc++.so.6 in my /usr/bin: https://paste.ofcode.org/gfZJwx4puVx7Uxy9a7BBU3 - don't forget those please :)
Of course, if there are ways to keep more or less the same semantics, while increasing performance, by all means it should be done !
But as has been noted, that's a lot of failures and exceptions may not be the appropriate mechanism to deal with this type of failure.
So, not to be rude here, but this may be a real-world scenario in your world, but not in everyone else’s. I get the sense the kind of stuff you’re working on would benefit from things like using assembly as well. But for desktop apps, games, compilers, other command line tools… who cares about the performance of throwing exceptions?
Note that I am trying to fix the unwinding problem, I have submitted a patch to libunwind to eliminate the contention during unwinding:
https://reviews.llvm.org/D120243
It require application support, unfortunately, as there is currently no way that libunwind can figure out if a shared library has been added or removed. But if you are willing to indicate that from within the application the performance problem is mostly fixed. The memory allocation issue remains, but I can live with that. I cannot live with single threaded unwinding.
Is there a problem if dynamic libraries invoke dlopen/dlclose they also need to call the sync function - correct?
I'm asking because we've developed a Common Lisp implementation that interoperates with C++ and it uses exception handling to unwind the stack (https://github.com/clasp-developers/clasp.git). We hit this global lock in unwinding problem a lot - it causes us a lot of grief.
I only use exceptions for cleanly unwinding the stack when an unrecoverable error occurs. I design and implement my code so that is rare.
Code that uses exceptions like this is a code smell, especially when performance is important. Using exceptions as control flow instead of if/loops is not a good design, IMO.
Note that this is in C++. I consider this style of writing not idiomatic, despite the STL doing it. I use either error return codes or std::optional (itself having a set of problems but IMO better than exceptions).
I'm more willing to accept this kind of code in Java, where it's more or less idiomatic. Less so in C#, where the tendency in the last 15 years has been to use the Try__ method instead of throwing exceptions.
You could argue we were using "too many exceptions", but to me it's the obvious way to unwind in C++.. except it's not fast enough, so we had to switch to a proto-Rust (this was before Rust) style system.
My argument against exceptions is basically everything other than performance. To make a performance decision you have to build the code incrementally with feedback from how it is actually going to be used e.g. if almost every parse fails then you probably don't want to throw whereas if your code almost always succeeds you probably want your register back (i.e. use exceptions)
The articles suggestion that it's only a matter of time for 128+ core CPUs to be common, at least in the server space, is probably the least contentious argument it makes?
Also, the scaling problems show up far before 128 threads are used. The scaling issues start showing up at just 4-8 threads, depending on the error rate.
Also also, C++ is used in a hell of a lot more places than just consumer desktops. So the current limitations of consumer desktops is fully irrelevant anyway.
You can get a machine with dual 64-core AMD EPYC 7662 CPUs[0] for a total of 128 cores. It will cost you almost $20k, though—for the most basic configuration.
[0] https://bizon-tech.com/bizon-x6000.html#959:8714;960:4858;96...