为什么抛出异常这么慢

why throw an exception is so slow

本文关键字:抛出异常 为什么      更新时间:2023-10-16
int foo(int n)
{
    //if (n==0) throw 0;  // throw is so slow
    if (n==0) return 0;   //return is faster
    //foo(n-1); sorry , it should be ..
    return foo(n-1);
}
int main()
{
  for (int i=0;i<50000;++i)
  {
      try{
          foo(80);
      }
      catch(...){
      }
  }
  return 0;
}

当我使用返回 0 时,for 循环执行得非常快,而使用 throw 0,执行速度非常慢。我知道异常处理效率不高,但我很惊讶它太慢了。

不要使用 throw 作为返回

你的代码也有缺陷,我假设你想返回一些东西n!=0

抛出异常是一种复杂的机制,可能是编译器后端中最难处理的部分。它涉及倒带堆栈,调用范围内对象的析构函数,可能处理大多数平台上的安全检查并找到可以处理异常的上行框架。

在您的情况下,它可能更快一些,但远不如呼叫返回快。

将 gcc 的优化版本与 return 0 进行比较:

foo(int):                                # @foo(int)
    xorl    %eax, %eax
    ret
main:                                   # @main
    xorl    %eax, %eax
    ret

并且版本例外(两种情况下都打开了 -O3 优化(:

foo(int):                                # @foo(int)
    pushq   %rbp
    movq    %rsp, %rbp
    movl    $4, %edi
    callq   __cxa_allocate_exception
    movl    $0, (%rax)
    movq    %rax, %rdi
    movl    typeinfo for int, %esi
    xorl    %edx, %edx
    callq   __cxa_throw
main:                                   # @main
    pushq   %rbp
    movq    %rsp, %rbp
    pushq   %rbx
    pushq   %rax
    movl    $-1, %ebx
.LBB1_1:                                # =>This Inner Loop Header: Depth=1
    incl    %ebx
    cmpl    $49999, %ebx            # imm = 0xC34F
    jg  .LBB1_4
    movl    $4, %edi
    callq   __cxa_allocate_exception
    movl    $0, (%rax)
    movq    %rax, %rdi
    movl    typeinfo for int, %esi
    xorl    %edx, %edx
    callq   __cxa_throw
    movq    %rax, %rdi
    callq   __cxa_begin_catch
    callq   __cxa_end_catch
    jmp .LBB1_1
.LBB1_4:
    xorl    %eax, %eax
    addq    $8, %rsp
    popq    %rbx
    popq    %rbp
    ret
GCC_except_table1:
    .byte   255                     # @LPStart Encoding = omit
    .byte   3                       # @TType Encoding = udata4
    .byte   175                     # @TType base offset
    .zero   1,128
    .zero   1
    .byte   3                       # Call site Encoding = udata4
    .byte   39                      # Call site table length
    .long   .Lset0
    .long   .Lset1
    .long   0                       #     has no landing pad
    .byte   0                       #   On action: cleanup
    .long   .Lset2
    .long   .Lset3
    .long   .Lset4
    .byte   1                       #   On action: 1
    .long   .Lset5
    .long   .Lset6
    .long   0                       #     has no landing pad
    .byte   0                       #   On action: cleanup
    .byte   1                       # >> Action Record 1 <<
    .byte   0                       #   No further actions
    .long   0                       # TypeInfo 1

正如你所看到的,代码更加复杂:设置和分配着陆垫,个性例程完成它们的工作,等等。而且这些东西也很难优化。您为使用的内容付费

以下是 gcc 编译您的代码的内容,并带有foo return 0;(有或没有修复以确保foo总是返回一些东西,尽管它在技术上是 UB 没有修复(:

main:
    xorl    %eax, %eax
    ret

编译器可以证明循环不会产生可观察到的副作用,并抛弃整个事情。