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'.
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'.
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.
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...
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.