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 for
    noexcept
    ,不必展开堆栈会有所帮助。@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