3 ms·
TLDR: avoid using C++ exception if you can?
by Mikhail_K 2y ago
TLDR: avoid using C++ exception if you can?
- gpderetta 2y agoOn the contrary, after the scalability patches got through, the performance impact of exceptions even at high failure rate is in the noise.
- bayindirh 2y agoNo, the only TLDR I got from that is, exceptions are should be reserved for exceptional cases. Their code is borderline abusing them, probably out of necessity. However, in this case this abuse provided a nice boost for everyone, so I can't complain.
- menaerus 2y agoAs always, and generally speaking, if anything they show that drawing the line is difficult since they show in their paper [1] that both std::expected and boost::LEAF are worse than std::exception. Excerpts from paper: > Single threaded the fib code using std::expected is more than four times slower than using traditional exceptions. > This has much less overhead than std::expected, but it is still not for free. For fib we see a slowdown of approx. 60% compared to traditional exceptions, which is still problematic. Now, admittedly, these workloads are too simple (sqrt and fibonacci) and only shown for the single-threaded use-cases but I guess you have to start from something that is easy enough to reason about otherwise getting to any sane conclusions might be too difficult. However, they continue to show in their blog how they significantly sped up the std::exception implementation in a very non-trivial workload - multi-threaded and JIT'ed database kernel. So, to prove the point about exceptions being slower in complex workloads, such as the one above, somebody would actually have to rewrite their whole codebase to use something else such as std::expected, return values or whatever. Non-trivial to say the least. [1] https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2544r0.html https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p25...
- willvarfar 2y agoThe article says: "Unwinding now scales with the number of cores, and we can safely use C++ exceptions even on machines with large core counts."
- protomolecule 2y agoNot at all. Basically, a naive implementation of exceptions uses a lock to guard unwind tables, and this scales poorly. The lock is needed only because .so libraries can be loaded and unloaded dynamically. One solution is to say that after a certain moment, the program doesn't load/unload dynamic libraries and doesn't use the lock after that. That's what ScyllaDB and Yandex[0] do, for example. Another solution is to just use the new glibc which fixes this issue. That aside, both exceptions and error codes introduce overhead, but they do it differently: exceptions add most[1] of their overhead when an error happens, while checking error codes adds overhead when an error doesn't happen[2]. One might conclude that exceptions should be used when errors are exceptionally rare; otherwise, use error codes. Or, just reclassify errors that happen too often as something you expect to happen, return them as a case of valid data and keep using exceptions. [0] https://habr.com/en/companies/yandex/articles/852244/ https://habr.com/en/companies/yandex/articles/852244/ [1] https://habr.com/en/companies/jugru/articles/494986/ https://habr.com/en/companies/jugru/articles/494986/ [2] https://archive.is/bIU23 https://archive.is/bIU23
- adrian_b 2y agoChecking error codes adds overhead only in the way it is implemented in modern programming languages, by returning an integer error code that must be tested for a conditional jump after returning to the invoking procedure. Many early programming languages, including various dialects of Algol and Fortran, included a better implementation method. The invoking procedure passed to the invoked procedure not a single return address, but multiple return addresses. One return address was for the normal return and the other addresses were for alternate returns when errors were detected. On a modern CPU with many registers, all these return addresses would be passed in registers. The invoking procedure had at its end one or more handlers for error conditions, corresponding to the alternate return addresses. When a procedure was invoked, no tests were done at the invocation place, because any error would cause a jump to the error handler. In the invoked procedure, a test and a conditional jump must always exist in order to detect an error, and there the alternate return was executed only when an error was detected, so this implementation could omit one test and one conditional jump in comparison with the modern implementations. Moreover, the text of the procedure was more clear in this way, with all the error handlers separated from the normal execution case, but nonetheless close enough to examine them when necessary.
- bluGill 2y agoNo use exceptions. In micro benchmarks we have known for years exceptions are slow. However more recient realistic benchmarks have discovered exceptions are generally faster than the checking - assuming you write all the if statements needed to unwind errors to where they can be handled - nobody does that in practice and so exceptions are slower than ignoring errors is what we were really saying when calling exceptions slow.
- Leherenn 2y agoI have to admit I have a hard time relating your comment to my experience. I remember a case where we were using a library throwing exceptions when it could not parse some string. I think we were parsing IP addresses, or something like this. There was a case where this was called in a hot loop (like thousands of IP addresses in a roe) and for whatever reason in some cases most of them were invalid, thus throwing. Then simple error handling: the caller catches the exception, logs the error and checks the next one. The program was frozen for seconds. Replacing the parser with the same logic but returning a bool instead was something like 1000x faster. That was on MacOs Clang Arm. I don't see how a few extra ifs (though none were required in this case) would change anything. Generally I'm not a fan of exceptions, you never what something is going to throw. The majority of the crashes we see in production are unhandled exceptions. Turned out the documentation did not mention that this function could also throw std exceptions on top of the lib ones in this specific case.
- saurik 2y agoYou can always find a case where you are stressing the very thing that people are using in a micro-benchmark; like, no one is claiming that table unwinding is the correct choice for every single function! But, the other extreme you get is saying all error handling should be done with checks and branches, and the idea is that this slows down the code which isn't throwing lots of exceptions so much that, for average workloads, it is the wrong tradeoff. Ideally, you could make this decision more locally, and yet continue to use the cleaner syntax which comes from structured error handling, but I don't know of any language which implements this :/. And, so, the best we have is to just use a language which maps exception syntax to tables as you can always -- as you did -- fix the few places it matters to use a result type by adding manual checks.
- liontwist 2y agoUse C. The effective and productive language which is easier to understand than a single feature of C++ like exceptions or initialization.
- einpoklum 2y agoHow about: "Make sure your performance-critical code / tight-loops don't do things which can throw exceptions, and then you won't have to worry (much) about the performance of exceptions" ? It may not be what the article says, but it's how I avoid having to delve into exception performance.