thomas
thomas

Reputation: 523

why throw an exception is so slow

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;
}

I tested above program. when I use return 0, the for-loop executed very fast, while use throw 0, very very slow. I know that exception-handle is not efficient, but I am surprized that it is so slow.

Upvotes: 0

Views: 220

Answers (2)

Marco A.
Marco A.

Reputation: 43662

Don't use throw as a return

Your code is also flawed, I'm assuming you wanted to return something for n!=0

Throwing exceptions is a complex mechanism and probably the hardest part to deal with in a compiler's back-end. It involves rewinding the stack, calling destructors of the objects in scope, probably dealing with security checks on most platforms and finding an upframe which can handle the exception.

In your case it might be somewhat faster but not nearly as fast as a call return.

Compare the optimized version for gcc with return 0:

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

main:                                   # @main
    xorl    %eax, %eax
    ret

and with the exception version ( -O3 optimizations on in both cases ):

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

As you can see the code is way more involved: landing pads are set and allocated, personality routines do their job, etc.. take a look at here. And this stuff is also hard to optimize. You pay for what you use.

Upvotes: 2

T.C.
T.C.

Reputation: 137425

Here's what gcc compiles your code to, with a return 0; in foo (with or without a fix to ensure that foo always returns something, though it's technically UB without a fix):

main:
    xorl    %eax, %eax
    ret

The compiler can prove that the loop produces no observable side effects, and throw out the whole thing.

Upvotes: 2

Related Questions