C++ 为什么不';t throw()和noexcept有开销吗?
C++ 为什么不';t throw()和noexcept有开销吗?,c++,performance,c++11,C++,Performance,C++11,throw()是作为异常说明符添加到C++03中的,但是在C++11中,它因noexcept说明符而被弃用 在使用throw()、noexcept和普通函数分析一些代码以查找速度之后,我发现所有这些函数调用的时间大致相同 结果: throw() noexcept plain old function 11233 ms 11105 ms 11216 ms 11195 ms 11122 ms 11150 ms 11192 ms
throw()
是作为异常说明符添加到C++03中的,但是在C++11中,它因noexcept
说明符而被弃用
在使用throw()
、noexcept
和普通函数分析一些代码以查找速度之后,我发现所有这些函数调用的时间大致相同
结果:
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
以下是我用来分析的代码:
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;
}
我对这些结果感到惊讶,因为我觉得throw()
和noexcept
给函数增加了一些开销
为什么与常规函数调用相比,
throw()
和noexcept
不会给函数调用增加任何开销?gcc和clang的代码生成非常相似,因为它们基本上是ABI兼容的。我只想用叮当声回答您的问题,但我的回答应该非常适用于您的gcc编译器
可以使用-S
命令行标志反汇编throw_func
、noexcept_func
和std_func
。这样做之后,您将注意到这三个函数都生成非常相似的程序集
差异包括:
\uuuuz10throw\u funcv
、\uuuuz13noexcept\u funcv
和\uuuuz8std\u funcv
throw_func
和noexcept_func
将在代码后生成“异常表”。这些表指示低级C++运行时库如何展开堆栈。这包括关于必须运行哪些析构函数、尝试哪些catch块以及在这两个函数的情况下,如果异常试图传播出去,该如何做的说明throw\u func
将包含对\uuuuuuucxa\u call\u unexpected
的调用,noexcept\u func
将包含对\uuuuuuuu clang\u call\u terminate
的调用(对于将命名为其他名称的gcc)std_func
将不包含对意外的或终止的调用。它也不会有“例外表”。没有要运行的析构函数,没有要插入的try/catch子句,也没有必要调用意外的
或终止
main
执行。在实际代码中,仅添加的代码大小就可能影响运行时性能。然而,对于经常执行且小到足以放入缓存的代码(如本测试),代码大小命中不会导致任何运行时性能命中
为了完整起见,这里是noexcept_func
程序集。下面的所有内容都是处理异常路径LBB0\u 1:
通过Ltmp1:
引发异常<代码>Ltmp2:是一个“平台”,如果异常试图在此处解除,运行时将分支到该平台。而GCC\u except\u表0:
是异常表本身
.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
.globl\uuuz13无异常功能
.对齐4,0x90
__Z13无异常功能:##@Z13无异常功能
.cfi_startproc
.cfi_个性155,uuuuuuuugxx_个性v0
Leh_func_begin0:
.cfi_lsda 16,Lexception0
##BB#0:
pushq%rbp
Ltmp3:
.cfi_def_cfa_偏移量16
Ltmp4:
.cfi_抵销%rbp,-16
movq%rsp,%rbp
Ltmp5:
.cfi_def_cfa_寄存器%rbp
移动值(%rip),%rax
leaq 1(%rax),%rcx
movq%rcx,_val(%rip)
测试质量%rax,%rax
je LBB0_1
LBB0_2:
popq%rbp
retq
LBB0_1:
movl$1,%edi
callq\uuuuuuucxa\u分配\u异常
Ltmp0:
莫沃__ZTI9exception@GOTPCREL(%rip),%rsi
xorl%edx,%edx
movq%rax,%rdi
callq uuuuuuuucxa uuuo掷球
Ltmp1:
jmp LBB0_2
LBB0_3:
Ltmp2:
movq%rax,%rdi
呼叫呼叫终止
.cfi_endproc
Leh_func_end0:
.section\uuuu TEXT,\uuuuu gcc\u除\u选项卡外
.对齐2
GCC_除表0外:
Lexception0:
.byte 255##@LPStart Encoding=省略
.byte 155##@t类型编码=间接pcrel sdata4
.asciz“\242\200\200”##@t类型基偏移量
.byte 3##调用站点编码=udata4
.byte 26##调用站点表长度
Lset0=Leh_func_begin0-Leh_func_begin0###>>调用站点1>调用站点2>操作记录1>捕获类型信息正确的语句将更接近throw()
,主要是noexcept
在一般情况下,不要删除太多开销。虽然编译器似乎可以避免对该函数进行异常处理,但如果抛出异常,它必须调用terminate这一事实意味着它不能真正调用。@DavidRodríguez dribeas fornoexcept
,不必展开堆栈会有所帮助。@T.C.noexcept
只比throw()快
当它被异常触发时?@Phantom:这取决于编译器的异常处理实现。如果函数调用中有额外的开销来允许展开,那么noexcept
可以避免这种情况。然而,现在大多数编译器使用静态构建的展开表,当没有抛出异常时,这些表没有运行时开销,只有很小的空间和链接时间overhead@T.C.我想我的评论有点不幸。这样做的目的是要澄清,如果有的话,它们将提高性能,而不是增加成本。重读我在深夜写的这篇文章,这篇评论远远不够好:)总而言之:差异存在于给定测试用例中无法访问的代码路径中,因此运行时间在统计上是相同的
.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