Despite many benefits, most people still do not prefer to use exceptions due to its overhead. So let's understand the overheads with benchmark code:
static void without_exception(benchmark::State &state){
for (auto _ : state){
std::vector<uint32_t> v(10000);
for (uint32_t i = 0; i < 10000; i++) v.at(i) = i;
}
}
BENCHMARK(without_exception);//----------------------------------------
static void with_exception(benchmark::State &state){
for (auto _ : state){
std::vector<uint32_t> v(10000);
for (uint32_t i = 0; i < 10000; i++){
try{
v.at(i) = i;
}
catch (const std::out_of_range &oor){}
}
}
}
BENCHMARK(with_exception);//--------------------------------------------
static void throwing_exception(benchmark::State &state){
for (auto _ : state){
std::vector<uint32_t> v(10000);
for (uint32_t i = 1; i < 10001; i++){
try{
v.at(i) = i;
}
catch (const std::out_of_range &oor){}
}
}
}
BENCHMARK(throwing_exception);//-----------------------------------------
- As you can see above,
with_exception
&without_exception
has only a single difference i.e. exception syntax. But none of them throws any exceptions. - While
throwing_exception
does the same task except it throws an exception of type std::out_of_range in the last iteration. - As you can see in below bar graph, the last bar is slightly high as compared to the previous two which shows the cost of throwing an exception.
- But the cost of using exception is zero here, as the previous two bars are identical.
Slice from 7 best practices for exception handling in Modern C++.
- I am not considering the optimization here which is the separate case as it trims some of the assembly instructions completely. Also, implementation of compiler & ABI plays a crucial role. But still, it is far better than losing time by setting up a guard(if(error) strategy) and explicitly checking for the presence of error everywhere.
- While in case of exception, the compiler generates a side table that maps any point that may throw an exception (program counter) to the list of handlers. When an exception is thrown, this list is consulted to pick the right handler (if any) and the stack is unwound.
- By the way, I am using a Google Benchmark, if you want to explore more.
- First and foremost, remember that using try and catch doesn't actually decrease performance unless an exception is thrown.
- It's "zero cost" exception handling – no instruction related to exception handling is executed until one is thrown.
- But, at the same time, it contributes to the size of executable due to unwinding routines, which may be important to consider for embedded systems.
TL;DR
No instruction related to exception handling is executed until one is thrown so usingtry
/catch
doesn’t actually decrease performance.
What's next?
Still not convinced of using exceptions?
You can try out tweaking same code provided above & check performance online with this tool. Don't forget to consider clang/GCC compilers, disassembly & optimization flags also while playing with it.
Wants to learn about exception handling in more detail?
Design your own tiny ABI for exception handling in C++.
Wants to see what Modern C++ offers in terms of exception safety?
Stay tuned here... :-).