为什么不抛出()和noexcept有任何开销?

pha*_*tom 9 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      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
Run Code Online (Sandbox Code Playgroud)

这是我用来描述的代码:

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;
}
Run Code Online (Sandbox Code Playgroud)

功能定义为:

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();
}
Run Code Online (Sandbox Code Playgroud)

我对这些结果感到惊讶,因为我的印象是,throw()并noexcept为该功能增加了一些开销.

为什么不throw()和noexcept相比于普通函数调用添加任何开销函数调用?

How*_*ant 13

gcc和clang具有非常相似的代码生成,因为它们在很大程度上是ABI彼此兼容的.我只能回答你的问题,但我的回答应该适用于你的gcc编译器.

可以反汇编throw_func,noexcept_func并std_func使用-S命令行标志.这样做,你会注意到这三个函数都生成了非常相似的程序集.

差异包括:

  1. 功能的名字将有所不同: __Z10throw_funcv,__Z13noexcept_funcv,和__Z8std_funcv.

  2. 除了标签名称之外,所有三个函数的"正常路径"汇编代码都是相同的.

  3. throw_func并将noexcept_func在代码后生成"异常表".这些表指示低级C++运行时库如何展开堆栈.这包括有关必须运行哪些析构函数的指令,要尝试的catch块,以及在这两个函数的情况下,如果异常试图传播出来该怎么办. throw_func将包含一个调用___cxa_call_unexpected,noexcept_func并将包含一个调用___clang_call_terminate(对于将被命名为其他内容的gcc).

  4. std_func不会包含对unexpectednor的调用terminate.它也没有"例外表".没有析构函数可以运行,没有try/catch子句登陆,也没有必要调用unexpected或terminate.

总之,这三个功能的唯一区别在于"特殊路径".而"特殊路径"增加了代码大小,但永远不会被你的代码执行main.在实际代码中,只是添加的代码大小会影响运行时性能.但是对于经常执行且足够小以适应缓存的代码(例如此测试),代码大小命中不会导致任何运行时性能损失.

为了完整性,这是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
Run Code Online (Sandbox Code Playgroud)

  • 总结一下:在给定测试用例中无法访问的代码路径存在差异,因此运行时间在统计上是相同的 (3认同)