在分析一些代码后,使用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; }
功能定义如下:
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(); }
解决方法
可以使用-S命令行标志来反汇编throw_func,noexcept_func和std_func.在这样做时,您将注意到,这三个功能都会产生非常相似的装配.
差异包括:
>这个函数的名称不一样:__Z10throw_funcv,__Z13noexcept_funcv和__Z8std_funcv.
>“正常路径”汇编代码对于所有三个函数都是相同的,除了可能是标签的名称.
> throw_func和noexcept_func将在代码之后生成“异常表”.这些表指示低级C运行时库如何解开堆栈.这包括关于什么析构函数必须运行的指令,要尝试的catch块,在这两个函数的情况下,如果异常尝试传播出来,该怎么做. throw_func将包含对___cxa_call_unexpected的调用,并且noexcept_func将包含对___clang_call_terminate的调用(对于将被命名为其他东西的gcc).
> std_func不会包含意外或终止的调用.它也不会有“例外表”.没有运行的析构函数,没有try / catch子句进入,没有必要调用意外或终止.
总之,这三个功能的唯一区别是在“异常路径”中.而“特殊的路径”增加了代码大小,但是永远不会被你的主机执行.在现实世界的代码中,增加的代码大小可能会影响运行时性能.然而,对于经常执行并且足够小以适应高速缓存(例如此测试)的代码,代码大小命中不会导致任何运行时性能命中.
为了完整,这里是noexcept_func程序集. LBB0_1以下的一切都是处理异常路径的. LBB0_1:通过Ltmp1:抛出异常. Ltmp2:是一个“着陆垫”,运行时将分支到一个异常尝试通过这里解开.而GCC_except_table0是异常表本身.
.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