Investigating the Performance Overhead of C++ Exceptions
pspdfkit.com
pspdfkit.com
Exceptions make your code faster in the non-exceptional case.
See all those if-branches for error checks that don't exist in your code? That branch-free code is going to run faster than your branchy code. And the code's shorter, because it doesn't have the checks and handling intermixed, which makes it fit better into cache.
This isn't rocket science, I'm amazed that this isn't obvious.
You can optimize the code generation around that knowledge but not passing it directly to the CPU
All major AMD and Intel processors do static branch prediction.
So, while the successfully predicted branches are pretty cheap, many fewer can be predicted. Synthetic benchmarks generally have many fewer branches to predict than live code has.
Ever since the 486 we've had zero-cost instructions, for example.
More pertinent to this, modern CPUs have multi-stage pipelines and branch prediction.
Third, just the possibility of throwing an exception has a cost as the program has to have the code to unwind the stack.
Last, humans have become increasingly bad at second-guessing compiler optimizations.
> This isn't rocket science, I'm amazed that this isn't obvious.
Thing is, it kind of is rocket science. And it's not obvious. That's why you have to test.
Now you could take the position that exception lead to better designed code and might have other such benefits. Personally, I've become more convinced over the years that exceptions are a false economy.
A correctly predicted branch is still slower than no branch. An incorrectly predicted branch is one of the slowest things you can have happen on a modern CPU.
> Third, just the possibility of throwing an exception has a cost as the program has to have the code to unwind the stack.
Incorrect. The exception handling code does not execute in the normal path.
-fno-exceptions ~never makes your code faster. It does, however, shrink your binary size. Which is a non-trivial cost, but not the focus of the discussion.
Per Agner Fog, misprediction on Ryzen will cost you 18 cycles. On Haswell family, 15 to 20. Floating point division can cost you more cycles, both in latency and throughput. Even just plain old L3 latency can be worse than misprediction.
No, on Ryzen, 32-bit float division, divps instruction, has latency 10 cycles, throughput 3 cycles. 64-bit float division, divpd instruction, has latency up to 13, throughput up to 5. Both faster than a cache miss, in latency and especially throughput.
> Even just plain old L3 latency can be worse than misprediction.
Yes. BTW, the slowest thing that can happen on modern CPU is not L3 access, it’s access to a cache line modified by another CPU core. Even slower than main memory latency.
true but the instruction fetcher is driven by the branch predictor, so a misprediction to rarely executed code in practice also implies a cache miss.
This is true only if only exceptional things throw exceptions. In some APIs normal, but not successful things throw exceptions. Things like file not found are often pretty normal, but may throw an exception.
There certainly are APIs that abuse exceptions, but IMHO far fewer than most Go (et al) programmers claim.
Exceptions are exactly same as errors, they're just a different way to handle them. Errors are natural to programming, because nearly every function may end up in two states: it successfully carried out the task (no error) or it failed to carry it out and here's the reason why (error).
The reason we have exceptions instead of errors is generally because we want the code to look syntactically sweet. A mere "a.b + c.d" in modern language may involve three method calls: to get "a.b", to get "c.d", and to add them using some custom definition of "+". Each of these methods may run into an error and exceptions provide a syntactically sweet way to handle this.
It's quite rare to want to handle an error condition half-way up the stack and do anything more than log / cleanup and resume unwinding (whether implicitly via exceptions or monadic errors or automatically via exception unwinding library).
You normally handle errors like file not found, couldn't connect, etc. near the leaf call which failed, if you want to handle them at all. So it makes sense to decide what error condition you want; do you abort the whole task (exception works great!), or do you do something interesting like retries or alternatives (exceptions not so hot, booleans or some kind of error code better reflects that the handler code is conditional rather than exceptional).
It may not be possible to make exceptions totally free, but the rest of the language may be slow enough so they won't stick out :) Example: Python.
The unwind info might end up calling destructors or exception handlers, which might or might not be on a separate page. You are probably referring to that.
> Incorrect. The exception handling code does not execute in the normal path.
But it does need to undo all allocations and such that were made between the exception and the nearest exception handler, doesn't it? Isn't this "unwinding the stack"?
it should do so if you use error codes too - else you've got an incorrect program which leaks things like memory (not too bad on our current computers), file handles (really sucks on windows), sockets (really sucks on linux).
On the other hand unwinding the stack is not complex and won't add much to program size.
But what you're referring to is a cost, yes, but only a cost when an exception happens which should be rare. And it's not that much more expensive of an unwind than what would happen if you `if (err = thing()) { return err; }` everywhere in your call stack anyway.
The demo benchmark, for example, would produce basically always missed branches. Well, if would if the compiler didn't just strip the empty branch, invalidating the entire benchmark.
Branch predictors steadily get better, sure, but they're not infallible. Remember that reducing the total number of branches in a program helps the branch predictor predict better.
For performance-sensitive code, I've yet to find a real-world case where exceptions out-perform exception-free code. Nor have I seen much in the way of contrary evidence among people that care about performance.
Otherwise, there are too many differences in detail to generalize. Rust can take advantage of reduced aliasing that C++ can't, but the compiler doesn't because that code is still too buggy to turn on.
Last time I compared carefully, Rust and C++ built with Clang had identical performance, to well under 1%: much smaller than, e.g., the difference between Clang and Gcc.
Use what fits for your situation, don't take the "when all I have's a hammer..." attitude to anything. Sometimes return codes make sense (eg in hot-loop code). Sometimes exceptions make sense (in constructors, for example. And no, two-step initialisation is not a good alternative.). Sometimes neither make sense and you actually should just terminate the whole program (out-of-memory). Sometimes contracts make more sense (precondition checks, validating function arguments).
Anyway, regardless of your strategy if you can provide some method of querying if a thing can succeed to your API, do so; it's always better to handle errors by just making sure you don't cause them!
If your error-handling code is triggered so often that you feel it might be causing a performance hit, you have far more serious problems than the performance overhead of how you handle errors.
Rule of thumb: if a code path is not exceptional then it should not be implemented with exceptions. The happy path is not an exception.
When I want a parameter is an integer, I don't check all the ways in which it's NOT an integer; the happy path is when it _is_ an int and the program can continue, and anything else is an _Error_. If it overflows, that's an error too.
i.e., happy means it's basically the longest path (aside from diagnosing the error).
The question is: should the subroutine set an error number or throw an exception? Knowing the cost for each might help me decide for various situations.
*for varying values of usually, YMMV, use your own judgement, etc
They should only be used in exceptional circumstances, but they appear to be littered among libraries for everything from telling the caller that the end of file has occurred to a connection timing out.
> The cost comes from having to track what automatic and temporary objects have been created and attaching them to the exception context so you can destroy them if an exception is thrown.
Doesn't it have to do that anyway, in case you return from a function, or break from inside a loop? Anytime you can leave a scope, it has to be able to do that.
> Another cost is having to track which base classes of a derived class have already been constructed when the derived constructor throws.
All of them have been constructed before you begin the derived constructor.
As for leaving the scope, you're right again and I was unclear. The problem is you are destroying automatic objects while returning from a scope and one of them throws. Now you have extra code and extra branches for all of that. The extra ways of exiting a scope generally bloat up the program, and the size and complexity of a function can prevent it from being inlined or otherwise optimized by the compiler. These are systemic performance concerns that won't just show up as hot spots on a profile.
As to your first paragraph: That's just calling the base class's destructor if the derived class's constructor throws. That's easy - that's what the base class's destructor is for. It's essentially one function call. The harder part (or so it seems to me) is correctly destroying the only-partially-constructed derived class.
So it is not forbidden but odds are you'll have your program terminate.
https://github.com/CppCon/CppCon2014/blob/master/Presentatio...
Instead of having a Constructor + init method you could have a static create function which returns an optional or a pair.
I have a hard time seeing in what case you would want to throw in a destructor though? Maybe I'm just writing a different kind of code :)
I can't imagine that case either - but what I can imagine is that your destructor calls a function that calls a function ... that calls a function you didn't realize can throw.
Point being it's not entirely orthogonal to "performance".
However value type exceptions might re-introduce it, just in a different form, with luck on C++23.
I'm a huge fan of checked exception, given a type system powerful enough to parametrize over them (C++ should be more than capable), so I'm looking forward to the development. Stroustrup is not a fan though and he might kill the effort.
> It should be well-known that actually throwing an exception in C++ is extremely costly.
Unfortunately it is well known, but without really understanding what "extremely costly" means. The article shows it is expensive compared to returning from a function that does almost nothing. But what about compared to allocating some memory? Or locking a mutex? Just recently here on Hacker News I recently saw someone worryung about using an exception to close a file after doing a bunch of reads because they thought it would be too slow – but, in reality, the time taken to read a whole file is astronomical compared to throwing an exception.
> C++'s "zero-overhead" principle
Now it's possible you're right here, but just to be clear: this principle just means features don't have a cost if you don't use them. For example, if you don't need a function to be virtual then you don't have to mark it as such, so the fact that C++ supports virtual methods didn't cost you anything. But if you do choose to mark a method as virtual then there will be costs involved (memory will be taken up with the vtable and pointers to it, invocations through a pointer will take longer). Similarly, the fact that throwing exceptions isn't zero cost does not violate this principle.
The reason I say you could still be right is that there is still a memory cost to including tables of unwinding information for exceptions even if you never throw them. Perhaps that's what you meant, but the surrounding text seems to be hinting at the cost of throwing them.
ETA: an uncontended mutex lock takes around 10-30ns depending on the machine. Any kind of exception is far more costly.
And there are plenty of other root causes for exceptions in real programs, not just allocations.
That does not mean one needs exceptions, though.
Then the program can do whatever cleanup is warranted, maybe freeing up a variety of other resources, and then maybe re-reserve that block, and forge ahead.
But when you actually care about performance, you often are not using the global allocator. A local allocation failure is nothing to panic over. Generally, only the higher level code -- where the handler is -- knows, or needs to care, what kind of allocator failed. The low-level code gets to just bail out, as it should.
Herb Sutter is, reliably, always right on his facts, but always mistaken on his opinions.
I am grateful for this. But I do wish the article had a few more comparisons like this, up to and including things much more expensive than exceptions, so readers could make their own decisions rather than just read about how terrible exceptions are.
As another data point, I just read that a Linux system call that does no actual work takes about 250ns, so about a fifth of the time for an exception from the article. Of course that's a totally unknown machine that could be much slower or faster than the one used for the article's benchmark. But I still found it interesting that they're the same order of magnitude – I hadn't expected exceptions to be quite that slow.
'zero-overhead' for some feature is equal to 'no runtime overhead above what one would incur from coding the feature yourself.
That definition explicitly does not include the concept that 'zero-overhead' features carry no cost when not used. In fact, the entire point of Bjarne discussing what "zero-cost" means in his response to Sutter's exception proposal, is to state that for C++ (which originated the term) "zero-cost"/zero-overhead does not mean that language features do not incur cost by inclusion in the language, like with exceptions. There is a cost to code during runtime if one does not explicitly remove exceptions from the language at compile time. Stroustrup's point is that this built in runtime overhead from having an exception mechanism built into C++ does not violate the language's 'zero-cost' guarantee.
"What you don’t use, you don’t pay for. And further: What you do use, you couldn’t hand code any better."
Ideally I would like to use exceptions as well, since they make the code look a lot neater and clearer, but I cannot justify their use in all places because of these non-zero costs.
The time to run exceptions is a lot less than to run failure checks, except in extreme cases as in the synthetic (not to say phony) benchmark presented. If the time spent throwing matters, you're Doing It Wrong.
The branch prediction slots that failure checks burn are rare and precious: much bigger than a general purpose register, and much faster than L1 cache. Blowing your branch prediction cache has costs hard to measure, although the perf registers help some.
The only substantial cost is object-code file size, and that mainly because precious little effort has been spent to minimize it, because really hardly anybody (i.e., only embedded) cares.
The problem with proving something like this is you have to build and maintain two separate sets of code for the same project, one that uses exceptions in the correct manner, and another which does error checking to the same degree. Nobody has time to do that for a significant project.
I don't know about NASA's case, but Google's style guide forbids exceptions only for legacy reasons. In fact it praises them:
"On their face, the benefits of using exceptions outweigh the costs, especially in new projects. However, for existing code, the introduction of exceptions has implications on all dependent code. ... Because most existing C++ code at Google is not prepared to deal with exceptions, it is comparatively difficult to adopt new code that generates exceptions."
Since I'm not working on Google's old code base I do use exceptions.
If you're throwing enough exceptions for this to matter it'll show up on a profiler, and then you can change that specific chunk of code to avoid treating that particular case as 'exceptional'.
Looking at the disassembly the machine code is ~2x the size for the exception versions, but most of it is on the cold path.
The exception version has a conditional branch to do the "== errorInt" part. The non-exception version manages to avoid the conditional branch by using a conditional move, which would avoid a pipeline stall on a branch mis-prediction.
Edit: I think this disproves desc's point ("If your application is slow <because of execptions> it'll show up on a profiler"). ie there's probably a small cost to exceptions even when they are not taken and it will be spread across your entire program and will not show as a single spike in a profiler.
I just built the following code with g++ v7.4 (from MSYS64 on Windows):
#include <math.h>
#include <stdexcept>
void exitWithMessageException() {
if (random() == 4321)
throw std::runtime_error("Halt! Who goes there?");
}
int main() {
exitWithMessageException();
return 1234;
}
The generated code mixed the exception handlers with the hot-path code. Here are the address ranges of relevant chunks: 100401080 - Hot path of exitWithMessageException
100401099
10040109a - Cold path of exitWithMessageException
10040113f
100401140 - Start of mainGCC 9 [1] instead moves the exception throwing branch into a cold clone of exitWithMessageException function. The behaviour seems to have changed on starting from GCC 8.x.
branches are usually superior to conditional moves for predictable conditions as they break dependency chains. In case the exceptional code path is taken, the cost of the misprediciton is dwarfed by the cost of unwinding the stack.
This is interesting actually, the fact that the compiler uses a conditional move in the error checking case could mean that the compiler has no useful branch probability model for that branch in the error checking case, but even when using __builtin_expect, the compiler still prefers the conditional move.
Interesting, not heard that before. Do you know of somewhere I can read about this?
Gcc absolutely won't generate two cmov instructions in a basic block. Clang, for its part, abandons practically all optimization of loops that could conceivably generate a throw.
Of course if you don't check for or otherwise handle errors, the program will be faster. It's literally doing less work.
This one adds functions that call the exception-based and error code based functions in a simple for loop. Both handle the error.
Unless I've screwed up somewhere, I think the result is that in the exception case, the body of the inner loop contains 13 instructions, while the error code case contains 5.
Also, the generated code for the exception case is harder to read and understand. When writing performance critical code I like to eye-ball the disassembly just to make sure the compiler didn't do anything unexpected. This task is hard enough already in non-trivial functions, I certainly don't want it getting any harder.
This is also the time one finds out that the exception handling code on typical C++ runtimes take out a global lock and the multithreaded application grinds to a halt—the raw CPU cost of an exception is not the most pressing problem at this point.
Do you know which runtimes do this? Or, which runtimes don't?
Bulk allocations with custom allocators would either be exactly what I said (low malloc calls) or would just not use malloc at all and use the system memory mapping functions.
The larger point was that an exception being thrown and taking a global lock is not going to tank performance. That would imply that all other threads are trying to take the same lock at the same time and that the thread that gets it holds it for a long time.
Even in the case of malloc, where there could be actual heavy lock contention, this is not always a bottleneck.
Says who? The C Standard says nothing of the kind.
not anymore for a couple years with glibc : https://www.phoronix.com/scan.php?page=news_item&px=glibc-ma...
https://twitter.com/TimSweeneyEpic/status/122307740466037145...
Quoting him in a follow up tweet:
>They weren’t throwing and a disassembly showed no visible artifacts of exceptions. But turning off the possibility of throwing exceptions gained 15% and just made the assembly code tighter with no clear pattern to the improvements.
Still, 'turn off exceptions because exceptions are slow' is a daft rule of thumb for the majority of software, where the slowness probably has more to do with choice of data structures, etc than compiler/platform implementation of language features.
Always measure first, last, and in between.
However, it appears to be a rather commonplace occurrence that having exceptions on, even if you aren't using them, can cause performance problems, it isn't just a one of in Tim's case. Also Tim has quite a bit of experience working on bleeding edge C and C++ performance code so there is a good chance he did account for this. You can ask him.
For what it's worth, the article itself has this bit:
"Thanks to the zero-cost exception model used in most C++ implementations (see section 5.4 of TR18015), the code in a try block runs without any overhead."
And if profiling does show up a case like that, the appropriate response to that is slapping noexcept on the function, not disabling exceptions globally.
Exceptions are meant to be fast if and only if you do not throw any. And even this is only truly true on Unix.
The original implementation of exceptions on Windows put extra unrolling info on the stack and then cleaned it up at the end of the function, adding a runtime cost even when no exceptions were thrown.
The Unix way of exception handling does not put anything extra on the stack, so exceptions are free if you don't throw any. The compiler puts some metadata into your binary and generates cleanup code. If an exception is thrown, the run-time (libstdc++ for gcc on Linux) will grab the return address from the stack and then do a binary search through the metadata to find the right unwinding code generated by the compiler. This is obviously a comparatively enormous overhead. In particular because the metadata are separated from the code, so the OS never has to read it from disk unless you actually throw an exception.
The reason for that is that you are not supposed to use exceptions for signaling. They are for error handling. Exceptions are for occasions like "you asked for memory but there was none left" or "you asked for the 15th element in this vector but it only has 10". They are not meant for non-exceptional errors like "you asked for data from the network but there is no data yet" or "you wanted me to check if the file exists and it does not".
The thinking behind all this is that the error handling path is not time critical. If it happens, something exceptional has happened, and the OS will probably have to swap in the error handling code from disk to begin with, which is horrendously slow. In fact, this is not just a possibility. The feature was designed specifically so that the linker would move the exception handling code to a different page and the OS would only read that page from disk if an exception actually occurs. This is not an accident. It is deliberate planning and took engineering effort to achieve.
Running a benchmark of throw vs return is an attempt to answer the wrong question.
Important side note: operator[] on a vector does not do range checking. at() does. That's why you should always use at() instead of operator[].
Once that data is in memory, you're back to the numbers in the article, where throwing an exception can take barely more than a microsecond. That's huge compared to a function call, but is fast compared to a lot of things that an overly cautious programmers wouldn't dare follow up with an exception. To steal your own example, checking whether a file exists certainly falls into this category – that's likely to take milliseconds.
(Even your other example – no data available to read (I'm imagining a select / poll situation) – would probably be fine with exceptions, though I agree it's not the right tool for that situation. Just making a call to the OS is likely to cost a comparible amount to throwing an exception, even though you're describing a non blocking call. And by definition this won't end to being called in a critical inner loop - if data becomes available then the exception will do being thrown.)
"The author of a library can detect errors, but does not in general have any idea what to do about them. The user of a library may know how to cope with such errors, but cannot detect them – or else they would have been handled in the user’s code and not left for the library to find. The primary purpose of the exception handling mechanism described here is to cope with this problem for C++ programs; other uses of what has been called exception handling in the literature are considered secondary. See the references for discussions of exception handling techniques and mechanisms."
The comment did say that, but the only explanation of why exceptions should only be used that way seemed to be performance based. If that is not the issue, then what is? Saying "that's what they're for" without any other detail is a circular argument.
I explained how exceptions are implemented, and the actionable intelligence you can derive from that understanding (if perf matters to your code and/or scenario, then use exceptions only for hard errors, not for generic signaling).
If perf does not matter in your scenario, than obviously the guidance does not apply to you. To me that does not mean it's a waste of time to explain how exceptions work and what that means to performance critical code.
Even if the metadata is loaded after the first time, the error handling code will probably still need to be fetched from disk.
Most programs are not performance critical and you can use whatever you want for exception handling. Heck, modern microservice based architectures use message busses and network sockets for error handling. I have seen code signal error conditions by writing things to a database.
By the way: Syscalls have become amazingly fast. Some syscalls like gettimeofday are now even done without switching to kernel mode at all. And the raw kernel mode switch is now in the order of hundreds of cycles. I read something like 300 cycles for a modern architecture.
Also note that while regular performance is probably not so important, in some areas like hard real-time requirements for automotive or aviation it might still be relevant that your code has a predictable maximum latency.
"The key takeaway here is that an exception cannot be ignored or forgotten; if we have an exception, it must be dealt with."
If converted to simple "C style" status returns -- it is only 5 times slower (11.5ns vs 57.5ns) -- and can be ignored and forgotten.
In the associated slide deck, boolean valid() must be used before value. In this case, this is just as unsafe as C return codes. A C++ exception is always considered an lvalue, meaning that union is initialized, but I am not sure what reading the other side results in (I don't think that this is allowed).
My take on this? Please use an exception if you mean an exception (for C++). If it is too slow (which I consider unlikely), please deal with it some other way (I would suggest a status return).
[[nodiscard]] -Werror
but a good way to deal with exceptions (in the very specific case of GUI desktop software, though that works also for server software that handles requests) is to just have a global catch handler at the top of your event loop which logs an error message / displays a message box to your user / does a preemptive backup save of the data in case something is deeply corrupted somewhere and then restarts the event loop.
There are couple of threads in dforum that discusses this and to have throw attribute [1] and eventually making nothrow as default [2].
[1] https://forum.dlang.org/thread/sbdrybtyfkxfhxxjgqco@forum.dl...
[2] https://forum.dlang.org/post/qur0l0$2h8s$1@digitalmars.com
See: https://droplet.fwsnet.net
Just experiment with adding and removing exceptions, and compare it. It's shocking.
Hello world using printf:
Instruction count: 14988
Binary size: 345 KB
Hello world try->throw->catch (and printf): Instruction count: 286926
Binary size: 397 KB
Hello world using std::cout: Instruction count: 63502
Binary size: 888 KBYou could submit a PR constexpr-ing std::cout initialization.
No. It's fastest to do that, but if you cared about fast, you wouldn't be throwing at all.
Exceptions, by definition, are off of the critical path. If there's a generalization to be made, it's that it's always best to catch them as far from the event as possible, because then you will have few catch clauses. If you have more than a few, you are Doing It Wrong.
It shows a fundamental misunderstanding to worry about how fast throwing an exception is. You legitimately worry about how fast it is not to throw them, and that is generally quite fast indeed.
Some compilers use setjump/longjump (sjlj), and then it needs to save all registers somewhere on every try{}. Probably also in every function that calls a destructor. That is very slow, even if no exception is thrown at runtime
EDIT: Is there something wrong with std::error_code? I don't care about my points, but I'm worried I might have missed something I should know.
So for cross-platform APIs that exist on both you'll generally use std::generic_category as the API is usually defined to modify errno in the case of an error. For Windows-only APIs use std::system_category.
2. Arguing about C++ exception performance is pretty pointless anyway since C++ performance is its (only) strong side. Until Java/C# matches C++ in e.g. cutting edge 3D engines, this question won't even become interesting.
This is why I find C++'s "nothrow" a joke, by the way. It's not within human power to guarantee that any piece of code never throws exceptions. A gamma ray might flip a bit. You never know. The only practical way to reliability is the Erlang way: be prepared for errors arising anywhere anytime, not rely on fallible notions of what can and cannot cause errors.
The difference between writing if (status != SUCCESS) and catch (Exception) is that the former will allow the program to actually crash when it should (see panic in Rust). You should not be able to recover from OutOfMemory or GammaRayFlippedABit errors (use ECC memory to prevent that). You should restart the process (or the entire machine) because you can't hope the developer was wise enough to destroy and recreate appropriate amount of state.
The additional problem with exceptions in Java and C# is that many times you don't care about failure of some operations, but the language practically forces you to write catch-all statement to prevent a program crash, so you end up accidentally catching unrecoverable errors.
This is something that can be enforced by the language, it's just that C doesn't do so. The Zig language does, though. The result is somewhat similar to conventional exceptions. [0]
It would be possible to do the same in C if a convention were used that could be verified by static analysis. I don't know if this has ever been implemented, though.
> And then your beautiful ole error-cods returnin' function either takes down your whole application (which might result in someone dying while on life support in a hospital) or plods on with a successfull error code (leaving your program in an incorrect state).
That's not an accurate account of how critical systems are written.
Plenty are written in C, which has no support for exceptions. Some are written in the SPARK language, a subset of Ada, which essentially forbids the use of exceptions. [1]
> This is why I find C++'s "nothrow" a joke, by the way. It's not within human power to guarantee that any piece of code never throws exceptions.
That's not right. Code written in C is guaranteed never to throw an exception, as the language doesn't support them. Error codes have to be used. Code can still go wrong, and can even produce undefined behaviour, but exceptions are never raised.
I'm not very knowledgeable on the particulars of nothrow and noexcept in C++ though, I have to admit. From a quick glance, it seems that it does provide a hard assurance that a noexcept function can never throw. If a noexcept function tries to throw, the effect is to call std::terminate. [2]
> A gamma ray might flip a bit.
That won't throw an exception, it will just flip a bit. Radiation-hardening is handled by hardware, and perhaps by replication, but not by software. [3]
> The only practical way to reliability is the Erlang way: be prepared for errors arising anywhere anytime, not rely on fallible notions of what can and cannot cause errors.
Again, that certainly isn't the only way. C and SPARK are both used for life-or-death code. No-one will ever write avionics software in Erlang.
[0] https://ziglang.org/documentation/master/#Errors
[1] https://docs.adacore.com/spark2014-docs/html/ug/en/source/la...
[2] https://en.cppreference.com/w/cpp/language/noexcept_spec
[3] https://en.wikipedia.org/wiki/Radiation_hardening#Examples_o...
And in some places, it's easier just writing it out than deal with the process to add a dependency.