Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overhead of try/finally in C#?

We've seen plenty of questions about when and why to use try/catch and try/catch/finally. And I know there's definitely a use case for try/finally (especially since it is the way the using statement is implemented).

We've also seen questions about the overhead of try/catch and exceptions.

The question I linked to, however, doesn't talk about the overhead of having JUST try-finally.

Assuming there are no exceptions from anything that happens in the try block, what's the overhead of making sure that the finally statements get executed on leaving the try block (sometimes by returning from the function)?

Again, I'm asking ONLY about try/finally, no catch, no throwing of exceptions.

Thanks!

EDIT: Okay, I'm going to try to show my use case a little better.

Which should I use, DoWithTryFinally or DoWithoutTryFinally?

public bool DoWithTryFinally() {   this.IsBusy = true;    try   {     if (DoLongCheckThatWillNotThrowException())     {       this.DebugLogSuccess();       return true;     }     else     {       this.ErrorLogFailure();       return false;     }   }   finally   {     this.IsBusy = false;   } }  public bool DoWithoutTryFinally() {   this.IsBusy = true;    if (DoLongCheckThatWillNotThrowException())   {     this.DebugLogSuccess();      this.IsBusy = false;     return true;   }   else   {     this.ErrorLogFailure();      this.IsBusy = false;     return false;   } } 

This case is overly simplistic because there are only two return points, but imagine if there were four... or ten... or a hundred.

At some point I would want to use try/finally for the following reasons:

  • Keep to DRY principles (especially as the number of exit points gets higher)
  • If it turns out that I'm wrong about my inner function not throwing an exception, then I want to make sure this.Working is set to false.

So hypothetically, given performance concerns, maintainability, and DRY principles, for what number of exit points (especially if I can assume that all inner exceptions are caught) do I want to incur whatever performance penalty is associated with try/finally?

EDIT #2: I changed the name of this.Working to this.IsBusy. Sorry, forgot to mention this is multithreaded (though only one thread will ever actually call the method); other threads will be polling to see if the object is doing its work. The return value is merely success or failure for if the work went as expected.

like image 864
Platinum Azure Avatar asked Nov 05 '10 14:11

Platinum Azure


People also ask

Does try-catch have overhead?

There has to be an overhead for try/catch blocks so that the CLR can handle the exceptions. C# runs on the . NET CLR(a virtual machine).

How costly is try-catch?

There is no cost to try/catch the only cost is when an exception is thrown, and that is regardless of whatever there is a try/catch around it or not.

Does try finally catch exceptions?

Yes, it absolutely will. Assuming your finally block doesn't throw an exception, of course, in which case that will effectively "replace" the one that was originally thrown.

Is try-catch slower?

Does try { } catch make program slower? No. There's a performance hit to actually throwing an exception, but if you don't catch it, it'll just propagate up and potentially be unhandled. In the case of exceptions you have no control over such as the standard or third …


2 Answers

Why not look at what you actually get?

Here is a simple chunk of code in C#:

    static void Main(string[] args)     {         int i = 0;         try         {             i = 1;             Console.WriteLine(i);             return;         }         finally         {             Console.WriteLine("finally.");         }     } 

And here is the resulting IL in the debug build:

.method private hidebysig static void Main(string[] args) cil managed {     .entrypoint     .maxstack 1     .locals init ([0] int32 i)     L_0000: nop      L_0001: ldc.i4.0      L_0002: stloc.0      L_0003: nop      L_0004: ldc.i4.1      L_0005: stloc.0      L_0006: ldloc.0 // here's the WriteLine of i      L_0007: call void [mscorlib]System.Console::WriteLine(int32)     L_000c: nop      L_000d: leave.s L_001d // this is the flavor of branch that triggers finally     L_000f: nop      L_0010: ldstr "finally."     L_0015: call void [mscorlib]System.Console::WriteLine(string)     L_001a: nop      L_001b: nop      L_001c: endfinally      L_001d: nop      L_001e: ret      .try L_0003 to L_000f finally handler L_000f to L_001d } 

and here's the assembly generated by the JIT when running in debug:

00000000  push        ebp  00000001  mov         ebp,esp  00000003  push        edi  00000004  push        esi  00000005  push        ebx  00000006  sub         esp,34h  00000009  mov         esi,ecx  0000000b  lea         edi,[ebp-38h]  0000000e  mov         ecx,0Bh  00000013  xor         eax,eax  00000015  rep stos    dword ptr es:[edi]  00000017  mov         ecx,esi  00000019  xor         eax,eax  0000001b  mov         dword ptr [ebp-1Ch],eax  0000001e  mov         dword ptr [ebp-3Ch],ecx  00000021  cmp         dword ptr ds:[00288D34h],0  00000028  je          0000002F  0000002a  call        59439E21  0000002f  xor         edx,edx  00000031  mov         dword ptr [ebp-40h],edx  00000034  nop          int i = 0; 00000035  xor         edx,edx  00000037  mov         dword ptr [ebp-40h],edx          try         { 0000003a  nop              i = 1; 0000003b  mov         dword ptr [ebp-40h],1              Console.WriteLine(i); 00000042  mov         ecx,dword ptr [ebp-40h]  00000045  call        58DB2EA0  0000004a  nop              return; 0000004b  nop  0000004c  mov         dword ptr [ebp-20h],0  00000053  mov         dword ptr [ebp-1Ch],0FCh  0000005a  push        4E1584h  0000005f  jmp         00000061          }         finally         { 00000061  nop              Console.WriteLine("finally."); 00000062  mov         ecx,dword ptr ds:[036E2088h]  00000068  call        58DB2DB4  0000006d  nop          } 0000006e  nop  0000006f  pop         eax  00000070  jmp         eax  00000072  nop      } 00000073  nop  00000074  lea         esp,[ebp-0Ch]  00000077  pop         ebx  00000078  pop         esi  00000079  pop         edi  0000007a  pop         ebp  0000007b  ret  0000007c  mov         dword ptr [ebp-1Ch],0  00000083  jmp         00000072  

Now, if I comment out the try and finally and the return, I get nearly identical assembly from the JIT. The differences you'll see are a jump into the finally block and some code to figure out where to go after the finally is executed. So you're talking about TINY differences. In release, the jump into the finally will get optimized out - braces are nop instructions, so this would become a jump to the next instruction, which is also a nop - that's an easy peephole optimization. The pop eax and then jmp eax is similarly cheap.

    { 00000000  push        ebp  00000001  mov         ebp,esp  00000003  push        edi  00000004  push        esi  00000005  push        ebx  00000006  sub         esp,34h  00000009  mov         esi,ecx  0000000b  lea         edi,[ebp-38h]  0000000e  mov         ecx,0Bh  00000013  xor         eax,eax  00000015  rep stos    dword ptr es:[edi]  00000017  mov         ecx,esi  00000019  xor         eax,eax  0000001b  mov         dword ptr [ebp-1Ch],eax  0000001e  mov         dword ptr [ebp-3Ch],ecx  00000021  cmp         dword ptr ds:[00198D34h],0  00000028  je          0000002F  0000002a  call        59549E21  0000002f  xor         edx,edx  00000031  mov         dword ptr [ebp-40h],edx  00000034  nop          int i = 0; 00000035  xor         edx,edx  00000037  mov         dword ptr [ebp-40h],edx          //try         //{             i = 1; 0000003a  mov         dword ptr [ebp-40h],1              Console.WriteLine(i); 00000041  mov         ecx,dword ptr [ebp-40h]  00000044  call        58EC2EA0  00000049  nop          //    return;         //}         //finally         //{             Console.WriteLine("finally."); 0000004a  mov         ecx,dword ptr ds:[034C2088h]  00000050  call        58EC2DB4  00000055  nop          //}     } 00000056  nop  00000057  lea         esp,[ebp-0Ch]  0000005a  pop         ebx  0000005b  pop         esi  0000005c  pop         edi  0000005d  pop         ebp  0000005e  ret  

So you're talking very, very tiny costs for try/finally. There are very few problem domains where this matters. If you're doing something like memcpy and put a try/finally around each byte being copied and then proceed to copy hundreds of MB of data, I could see that being an issue, but in most usage? Negligible.

like image 122
plinth Avatar answered Oct 13 '22 12:10

plinth


So let's assume there's an overhead. Are you going to stop using finally then? Hopefully not.

IMO performance metrics are only relevant if you can choose between different options. I cannot see how you can get the semantic of finally without using finally.

like image 33
Brian Rasmussen Avatar answered Oct 13 '22 12:10

Brian Rasmussen