throw()
was added in C++03 as an exception specifier, however it was deprecated in C++11 for the noexcept
specifier.
After profiling some code to find the speed using throw()
, noexcept
and just plain functions I found that all of them had roughly the same time for the function call.
Results:
throw() noexcept plain old function
11233 ms 11105 ms 11216 ms
11195 ms 11122 ms 11150 ms
11192 ms 11151 ms 11231 ms
11214 ms 11218 ms 11228 ms
compiled with MinGW using g++ -o test.exe inc.cpp no.cpp -std=c++11 -O3
Here is the code I used to profile:
int main() {
unsigned long long iter = (unsigned long long)1 << (unsigned long long)40;
auto t1 = std::chrono::high_resolution_clock::now();
for(unsigned long long i = 0; i < iter; i++)
{
#ifdef THROW
throw_func();
#elif defined NOEXCEPT
noexcept_func();
#else
std_func();
#endif
}
auto t2 = std::chrono::high_resolution_clock::now();
std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count() << " ms" << std::endl;
}
The functions are defined as:
unsigned long long val = 1;
struct exception { };
void throw_func(void) throw()
{
if (!val++)
throw exception();
}
void noexcept_func(void) noexcept
{
if (!val++)
throw exception();
}
void std_func(void)
{
if (!val++)
throw exception();
}
I was surprised by these results because I was under the impression that throw()
and noexcept
added some overhead to the function.
Why doesn't throw()
and noexcept
add any overhead to the function call as compared to the regular function call?
gcc and clang have very similar code generation as they are largely ABI compatible with one another. I only have clang to answer your question with, but my answer should apply to your gcc compiler fairly closely.
One can disassemble throw_func
, noexcept_func
and std_func
using the -S
command line flag. Upon doing so, you will note that the three functions all generate remarkably similar assembly.
Differences include:
The mangled name of the functions will differ: __Z10throw_funcv
, __Z13noexcept_funcv
, and __Z8std_funcv
.
The "normal path" assembly code will be identical for all three functions except perhaps for the names of labels.
throw_func
and noexcept_func
will generate "exception tables" after the code. These tables instruct the low-level C++ runtime library how to unwind the stack. This includes instructions on what destructors must be run, what catch blocks to try, and in the case of these two functions, what to do if an exception tries to propagate out. throw_func
will contain a call to ___cxa_call_unexpected
and noexcept_func
will contain a call to ___clang_call_terminate
(for gcc that will be named something else).
std_func
will not contain calls to unexpected
nor terminate
. Nor will it have an "exception table". There are no destructors to run, no try/catch clauses to land in, and there is no need to call unexpected
or terminate
.
In summary, the only difference in these three functions is in the "exceptional path". And the "exceptional path" adds code size, but is never executed by your main
. In real-world code, just the added code size can impact run-time performance. However for code that is executed often, and small enough to fit in cache (such as this test), the code size hit won't cause any run-time performance hit.
For completeness, here is the noexcept_func
assembly. Everything below LBB0_1:
is to handle the exceptional path. LBB0_1:
thru Ltmp1:
throws the exception. Ltmp2:
is a "landing pad" that the runtime will branch to if an exception tries to unwind through here. And GCC_except_table0:
is the exception table itself.
.globl __Z13noexcept_funcv
.align 4, 0x90
__Z13noexcept_funcv: ## @_Z13noexcept_funcv
.cfi_startproc
.cfi_personality 155, ___gxx_personality_v0
Leh_func_begin0:
.cfi_lsda 16, Lexception0
## BB#0:
pushq %rbp
Ltmp3:
.cfi_def_cfa_offset 16
Ltmp4:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Ltmp5:
.cfi_def_cfa_register %rbp
movq _val(%rip), %rax
leaq 1(%rax), %rcx
movq %rcx, _val(%rip)
testq %rax, %rax
je LBB0_1
LBB0_2:
popq %rbp
retq
LBB0_1:
movl $1, %edi
callq ___cxa_allocate_exception
Ltmp0:
movq __ZTI9exception@GOTPCREL(%rip), %rsi
xorl %edx, %edx
movq %rax, %rdi
callq ___cxa_throw
Ltmp1:
jmp LBB0_2
LBB0_3:
Ltmp2:
movq %rax, %rdi
callq ___clang_call_terminate
.cfi_endproc
Leh_func_end0:
.section __TEXT,__gcc_except_tab
.align 2
GCC_except_table0:
Lexception0:
.byte 255 ## @LPStart Encoding = omit
.byte 155 ## @TType Encoding = indirect pcrel sdata4
.asciz "\242\200\200" ## @TType base offset
.byte 3 ## Call site Encoding = udata4
.byte 26 ## Call site table length
Lset0 = Leh_func_begin0-Leh_func_begin0 ## >> Call Site 1 <<
.long Lset0
Lset1 = Ltmp0-Leh_func_begin0 ## Call between Leh_func_begin0 and Ltmp0
.long Lset1
.long 0 ## has no landing pad
.byte 0 ## On action: cleanup
Lset2 = Ltmp0-Leh_func_begin0 ## >> Call Site 2 <<
.long Lset2
Lset3 = Ltmp1-Ltmp0 ## Call between Ltmp0 and Ltmp1
.long Lset3
Lset4 = Ltmp2-Leh_func_begin0 ## jumps to Ltmp2
.long Lset4
.byte 1 ## On action: 1
.byte 1 ## >> Action Record 1 <<
## Catch TypeInfo 1
.byte 0 ## No further actions
## >> Catch TypeInfos <<
.long 0 ## TypeInfo 1
.align 2
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With