Error codes are far slower than exceptions(lordsoftech.com)
lordsoftech.com
Error codes are far slower than exceptions
https://lordsoftech.com/programming/error-codes-are-far-slower-than-exceptions/
21 comments
Nobody seems to mention this when comparing, but code to handle the case of getting a bad error code is almost always very poorly exercised: therefore, buggy.
That is in contrast to the code run during an exception, the destructors, which run all the damn time.
If you do make the effort to test your error-handling code, that is a significant expense on top of the reduced performance. Maintaining those tests as the code changes from under them is another expense.
That is in contrast to the code run during an exception, the destructors, which run all the damn time.
If you do make the effort to test your error-handling code, that is a significant expense on top of the reduced performance. Maintaining those tests as the code changes from under them is another expense.
Destructors/RAII and finally statements work just as well when returning error codes as they do with exceptions.
Exactly this. It's hardly ever mentionned, but as error paths are less tested, code that is written without exceptions (and RAII) will be wrong a lot more often.
This is something that will also be covered in the talk by Raphael Poss of Cockroach Labs for the upcoming Go systems conference (https://systemsconf.io?utm_source=chewxyhn&utm_campaign=go_s...)
The conclusion is that panics have less overheads than returning errors. I'm curious to actually hear more
The conclusion is that panics have less overheads than returning errors. I'm curious to actually hear more
Could you compile methods with error codes into two versions - one that handles error codes and one that ignores them. Then when you want to return an error code, you also transfer control to the version of each method that handles the codes.
That's basically how exceptions work in a modern C++ ABI. And it's why taking an exception is so slow--throw has to lookup the caller in a huge map to find the address of the caller's catch handler to jump to, then that function has to do the same, etc, all the way up the stack.
In principal exceptions should be much faster than explicit error checking, and as in the article this is often the case. But modern CPUs are really good at speculating through conditional branches. To achieve "zero cost" exceptions compilers often have to do non-trivial code reordering and duplication to segregate the non-exception and exception branches. Sometimes this necessary rewriting can have a negative impact on performance.
In principal exceptions should be much faster than explicit error checking, and as in the article this is often the case. But modern CPUs are really good at speculating through conditional branches. To achieve "zero cost" exceptions compilers often have to do non-trivial code reordering and duplication to segregate the non-exception and exception branches. Sometimes this necessary rewriting can have a negative impact on performance.
One feature I've wanted is an error return keyword to complement 'return val;' something like 'error_return val;' Then a feature where if a function does an error return but the caller doesn't check the error the compiler inserts a exception or a panic.
So you could write code like
So you could write code like
log_stuff(stuff); // on an error will panic.
var rtn = log_stuff(stuff);
if(rtn.error)
{
// handle error
}
try
{
log_stuff(stuff);
}
catch()
{
// jumps here on an error
}Definitely interesting. Makes me wonder about the performance of sum types for error handling (e.g., Result in Rust).
At lot of what we learned about performance in the old days is no longer true. I'd say every time I've fired up the profiler to try to speed something up I've gotten a surprise and rarely is it because of inefficient code on my part. (And the only one of those I recall is where I used n^2 routines knowing n was small--only the use had evolved, when I wrote it n was generally around 20 and almost never over 50. When I got asked to look into it there was a case where n was around 2000.)
Another example of dated performance ideas, linked lists aren't even better for many of their theoretically better use cases: https://www.youtube.com/watch?v=YQs6IC-vgmo
Slides and missing graph on slide 45 available at: https://sec.ch9.ms/ecn/events/GoingNative12/GN12Cpp11Style.p...
Slides and missing graph on slide 45 available at: https://sec.ch9.ms/ecn/events/GoingNative12/GN12Cpp11Style.p...
They are error codes.
I would guess it would be the same as error codes, as you have to go through the branch everytime to check if it's Err or Ok.
LLVM has/will soon have hot-cold splitting, I wonder how much the overhead of error codes would be reduced. Requires an extra step of PGO though.
https://llvm.org/devmtg/2019-10/slides/Kumar-HotColdSplittin...
https://llvm.org/devmtg/2019-10/slides/Kumar-HotColdSplittin...
Maybe that Google should update their styleguide then, they fail to mention this performance advantage
https://google.github.io/styleguide/cppguide.html#Exceptions
Really interesting article. For me this was an important takeaway:
>Although it should fail gracefully, it does not need to be optimised for failure.
>Although it should fail gracefully, it does not need to be optimised for failure.
99% of your code doesn't need optimization, period. If it's not inside at least two loops it almost certainly doesn't meaningfully contribute to the total runtime.
Except the one case where you have a slow approach diffused over a whole application, when all of the code needs an optimization.
That case is easy to hit with compilers or annotation and such features. It is still one thing to optimize but impact is typically big.
That case is easy to hit with compilers or annotation and such features. It is still one thing to optimize but impact is typically big.
That's only true for application code. If you write libraries, you can't know how your library functions will be called and have to assume that most of them could end up in performance-critical code paths.
> Error codes fail the test of “zero overhead for common cases; pay for play for uncommon cases”:
> There is calling convention impact. You now have two values to return (for non-void returning functions): the actual return value and the possible error. This burns more registers and/or stack space, making calls less efficient. Inlining can of course help to recover this for the subset of calls that can be inlined.
> There are branches injected into callsites anywhere a callee can fail. I call costs like this “peanut butter,” because the checks are smeared across the code, making it difficult to measure the impact directly. In Midori we were able to experiment and measure, and confirm that yes, indeed, the cost here is nontrivial. There is also a secondary effect which is, because functions contain more branches, there is more risk of confusing the optimizer.
> This might be surprising to some people, since undoubtedly everyone has heard that “exceptions are slow.” It turns out that they don’t have to be. And, when done right, they get error handling code and data off hot paths which increases I-cache and TLB performance, compared to the overheads above, which obviously decreases them.
[1] http://joeduffyblog.com/2016/02/07/the-error-model/