335

During a code review with a Microsoft employee we came across a large section of code inside a try{} block. She and an IT representative suggested this can have effects on performance of the code. In fact, they suggested most of the code should be outside of try/catch blocks, and that only important sections should be checked. The Microsoft employee added and said an upcoming white paper warns against incorrect try/catch blocks.

I've looked around and found it can affect optimizations, but it seems to only apply when a variable is shared between scopes.

I'm not asking about maintainability of the code, or even handling the right exceptions (the code in question needs re-factoring, no doubt). I'm also not referring to using exceptions for flow control, this is clearly wrong in most cases. Those are important issues (some are more important), but not the focus here.

How do try/catch blocks affect performance when exceptions are not thrown?

user247702
  • 23,641
  • 15
  • 110
  • 157
Kobi
  • 135,331
  • 41
  • 252
  • 292
  • 170
    "He who would sacrifice correctness for performance deserves neither." – Joel Coehoorn Aug 20 '09 at 19:52
  • 3
    Joel - I clearly said this isn't the focus, I'm not doing micro optimizations, and good code is more important to me, I know better than that (I'm the developer, no the IT guy). This is a technical question. – Kobi Aug 20 '09 at 19:58
  • 1
    so you are eating the exceptions, and not thowing them? Are you handling the failure case then or doing something? – Mike Ohlsen Aug 20 '09 at 19:58
  • I think someone should break out ILDasm and let us know. – Tom Ritter Aug 20 '09 at 19:59
  • 23
    that said, correctness need not always be sacrificed for performance. – Dan Davies Brackett Aug 20 '09 at 19:59
  • @mohlsen - no. there are no exceptions. The code works well, but surrounded by a try block. – Kobi Aug 20 '09 at 19:59
  • @Kobi - there are only two possible answers to your question: "yes" or "no". If "no" then the only possible use for the information is justify adding or leaving useless or cluttering try/catch blocks. If "yes", then the only possible use for the information is to justify removing try/catch blocks that probably should not be removed. – Joel Coehoorn Aug 20 '09 at 20:07
  • 31
    How about simple curiousity? – Samantha Branham Aug 20 '09 at 20:13
  • 85
    @Joel: Perhaps Kobi just wants to know the answer out of curiosity. Knowing whether performance will be better or worse doesn't necessarily mean he's going to do anything crazy with his code. Isn't the pursuit of knowledge for its own sake a good thing? – LukeH Aug 20 '09 at 20:15
  • Why is there a try...catch block if an exception isn't being thrown? Code smell. – user7116 Aug 20 '09 at 20:16
  • 1
    @Joel - the issue came up during a meeting, and is on our list. Personally I don't think it'll help, but I want more opinions. – Kobi Aug 20 '09 at 20:17
  • 1
    I've seen some really bad programming practices come out of MS. Don't take your acquaintance as an authority just because she worked there. – David Thornley Aug 20 '09 at 20:20
  • 2
    @David - That's why I said I never heard of such an issue, and that's why I'm asking this question. – Kobi Aug 20 '09 at 20:22
  • 1
    Knuth warned about premature optimization: http://en.wikiquote.org/wiki/Donald_Knuth – Ed Power Aug 27 '09 at 19:08
  • 1
    There seems to be a case, that to me is obvious, that people are not considering: A try/catch block that can let you know what/where the problem was when a program needs to abort and stop. You can run for years with never catching anything. But that doesn't mean you should remove the try/catch block. – Mark T Aug 28 '09 at 16:12
  • 10
    Here's a good algorithm for knowing whether to make this change or not. First, set meaningful customer-based performance goals. Second, write the code to be both correct and clear first. Third, test it against your goals. Fourth, if you meet your goals, knock off work early and go to the beach. Fifth, if you do not meet your goals, use a profiler to find the code that is too slow. Sixth, if that code happens to be too slow because of an unnecessary exception handler, only then remove the exception handler. If not, fix the code that is actually too slow. Then go back to step three. – Eric Lippert Aug 21 '09 at 17:09
  • If an unhandled error is thrown by an Office Customisation (aka VSTO), the Office application will catch it and throw a not so friendly message to the user. Try Catching these exceptions before Office does allows me to both log the error, send a friendly message to the user, and finally release any open ressources. Is this bad practice? – Ama Jan 03 '20 at 22:47
  • 1
    @Ama - Sounds like that's exactly what you should be doing. To be explicit - I asked if a `try`/`catch` block is bad - and the answer was *"No, it's great - use `try`/`catch`!"*. You definitely don't want these ugly error messages being displayed to your users. – Kobi Jan 04 '20 at 07:23
  • so why don't we try catch entire project? – Serdar Samancıoğlu Feb 06 '20 at 07:28
  • 1
    @SerdarSamancıoğlu you can, see here: https://learn.microsoft.com/en-us/dotnet/api/system.appdomain.unhandledexception?view=netframework-4.8; the real concern about Try..Catch is when you are expecting to Catch *often*. Because the App then builds a stack trace, which consumes resources. It also breaks the natural flow of your application, which is not recommended either. So in short you want to use these Try...Catch to cleanup errors, not to manage a logic flow. – Ama Jul 19 '20 at 00:59

13 Answers13

250

Check it.

static public void Main(string[] args)
{
    Stopwatch w = new Stopwatch();
    double d = 0;

    w.Start();

    for (int i = 0; i < 10000000; i++)
    {
        try
        {
            d = Math.Sin(1);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
        }
    }

    w.Stop();
    Console.WriteLine(w.Elapsed);
    w.Reset();
    w.Start();

    for (int i = 0; i < 10000000; i++)
    {
        d = Math.Sin(1);
    }

    w.Stop();
    Console.WriteLine(w.Elapsed);
}

Output:

00:00:00.4269033  // with try/catch
00:00:00.4260383  // without.

In milliseconds:

449
416

New code:

for (int j = 0; j < 10; j++)
{
    Stopwatch w = new Stopwatch();
    double d = 0;
    w.Start();

    for (int i = 0; i < 10000000; i++)
    {
        try
        {
            d = Math.Sin(d);
        }

        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
        }

        finally
        {
            d = Math.Sin(d);
        }
    }

    w.Stop();
    Console.Write("   try/catch/finally: ");
    Console.WriteLine(w.ElapsedMilliseconds);
    w.Reset();
    d = 0;
    w.Start();

    for (int i = 0; i < 10000000; i++)
    {
        d = Math.Sin(d);
        d = Math.Sin(d);
    }

    w.Stop();
    Console.Write("No try/catch/finally: ");
    Console.WriteLine(w.ElapsedMilliseconds);
    Console.WriteLine();
}

New results:

   try/catch/finally: 382
No try/catch/finally: 332

   try/catch/finally: 375
No try/catch/finally: 332

   try/catch/finally: 376
No try/catch/finally: 333

   try/catch/finally: 375
No try/catch/finally: 330

   try/catch/finally: 373
No try/catch/finally: 329

   try/catch/finally: 373
No try/catch/finally: 330

   try/catch/finally: 373
No try/catch/finally: 352

   try/catch/finally: 374
No try/catch/finally: 331

   try/catch/finally: 380
No try/catch/finally: 329

   try/catch/finally: 374
No try/catch/finally: 334
Anatoliy Nikolaev
  • 22,370
  • 15
  • 69
  • 68
Ben M
  • 22,262
  • 3
  • 67
  • 71
  • 31
    Can you try them in reverse order as well to be sure that JIT compilation hasn't had an effect on the former? – JoshJordan Aug 20 '09 at 20:04
  • 31
    Programs like this hardly seem like good candidates for testing the impact of exception handling, too much of what would be going on in normal try{} catch{} blocks is going to be optimized out. I may be out to lunch on that... – LorenVS Aug 20 '09 at 20:04
  • 1
    +1 For measuring. I wonder if a try/finally would have the same relative stats as a try/catch? – Joseph Aug 20 '09 at 20:04
  • 36
    This is a debug build. The JIT doesn't optimize those. – Ben M Aug 20 '09 at 20:11
  • 4
    Note that this doesn't seem to be the same as what the question is about. The question states that some of the code should be moved outside and only some pieces checked with exceptions. In other words, it's not about getting rid of the try/catch altogether, it's whether code inside try/catch runs slower *by itself* than outside. – Lasse V. Karlsen Aug 21 '09 at 17:14
  • I'm not really looking for empirical results, but this is interesting: I tried not to share d between scopes (`try {d = Math.Sin(d); d = Math.Sin(d); }`), and I actually get better results *with* try/catch. – Kobi Aug 22 '09 at 16:09
  • 8
    This is not true at all, Think of it. How many times that you use try catch in a loop? Most of the time you will use loop in a try.c – Athiwat Chunlakhan Aug 29 '09 at 20:10
  • -1, agree with Lasse V. Karlsen, this doesn't seem to be the question. – John M Gant Aug 31 '09 at 18:32
  • 9
    Really? "How do try/catch blocks affect performance when exceptions are not thrown?" – Ben M Aug 31 '09 at 18:48
  • The way I see it, yes, really. No offense intended, but the original question was about the placement of the try/catch blocks, not their existence. Your tests address the performance impact of their existence, not their placement. – John M Gant Aug 31 '09 at 19:27
  • 2
    Actually, if I may, that is also a part of the question. Should we worry about try/catch blocks, and will it help if that are moved/removed (though, clearly, they are important and would not be removed - you're right about that jmgant). Again, the scientist in me isn't not crazy about benchmarking (unless done on a huge scale), so I won't accept this answer, but Ben did get my +1. – Kobi Aug 31 '09 at 21:27
  • After careful thought I've decided to accept this answer. Although the benchmarking is iffy, I think this answer best relate to my question (Eric Lippert has an important point, but we already knew that). This approach, to my tests, shows inconclusive results - sometimes it runs faster with try/catch - and this is probably the best proof. – Kobi Sep 01 '09 at 04:45
  • According to this, the overhead of a `try`/`catch` block when no exception is thrown is less than 15% of the cost of a single call to `Math.Sin`. Not. Bad. :) – Sam Harwell Aug 02 '13 at 17:45
  • 2
    @BenM: If "This is a debug build", then it's useless for performance testing. And you didn't even mention that in the answer itself :( – Ben Voigt Apr 11 '14 at 15:11
  • 2
    @BenVoigt: you're misapplying a generally sound principle; in this case, a debug build removes any possibility that optimization has interfered with the results. – Ben M Apr 28 '14 at 21:54
  • 7
    @BenM: Optimization doesn't "interfere" with results. Optimized code are the only results we're interested in. – Ben Voigt Apr 28 '14 at 22:12
  • 2
    @BenVoigt: if optimization doesn't potentially interfere with (i.e. change) results, then what's your objection to a debug build? At any rate, the purpose of avoiding optimization *in this case* is to remove a variable from the test; we can be reasonably confident that any observed difference is directly attributable to the presence of try/catch statements, and not (for example) different code generation strategies employed by the optimizer. – Ben M Apr 29 '14 at 03:42
  • @BenM: Avoiding optimization *adds* a variable. The actual behavior of your release will vary from your benchmark results. The question is all about whether try/catch slows things down, and inhibiting optimization is one of the most important ways it might slow things down. The question even said that. – Ben Voigt Apr 29 '14 at 05:43
  • 4
    @BenVoigt: The OP wanted to know about the performance hit of try/catch blocks *in general* - clearly, enabling optimization here would have made the results worthless as a general measure of that hit since the behavior/benefit of optimization is highly context-specific. I already doubt the value of this test as a general measure since it's a single benchmark of very narrow conditions; the optimizer would have specialized it even further, making it even less useful as a general predictor. – Ben M Apr 29 '14 at 12:45
  • 1
    @BenM can you please provide more confident results? I mean your asnwer is first in google so it's very important to be correct. Please, use some bench framework instead of manual `swopwatching`. It will provide more info - mean, variance, confidence and so on. – Alex Zhukovskiy Jun 07 '16 at 16:04
  • 1
    @AlexZhukovskiy Seriously? – Ben M Jun 09 '16 at 16:16
  • 3
    @BenM well, you can ignore it, but unfortunly in this case these measurments are pretty useless. – Alex Zhukovskiy Jun 10 '16 at 08:54
  • Just updating on the answser as it's the first that come out on google. I did remove all try-catch from real life application and i've seen between 8% and 27% gain in performance just by ensuring value pass would not trigger errors. On long running process that takes up to 8 days we shaved between 12 hours to 50 hours of execution time. – Franck Jan 12 '18 at 19:05
  • 1
    So I ran it WITH JIT Optimizations Enabled with rather surprising results: `00:00:00.0196374 //try catch` `00:00:00.0035727 //no try catch` – Tobias Brohl Jun 19 '18 at 08:38
117

After seeing all the stats for with try/catch and without try/catch, curiosity forced me to look behind to see what is generated for both the cases. Here is the code:

C#:

private static void TestWithoutTryCatch(){
    Console.WriteLine("SIN(1) = {0} - No Try/Catch", Math.Sin(1)); 
}

MSIL:

.method private hidebysig static void  TestWithoutTryCatch() cil managed
{
  // Code size       32 (0x20)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldstr      "SIN(1) = {0} - No Try/Catch"
  IL_0006:  ldc.r8     1.
  IL_000f:  call       float64 [mscorlib]System.Math::Sin(float64)
  IL_0014:  box        [mscorlib]System.Double
  IL_0019:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                object)
  IL_001e:  nop
  IL_001f:  ret
} // end of method Program::TestWithoutTryCatch

C#:

private static void TestWithTryCatch(){
    try{
        Console.WriteLine("SIN(1) = {0}", Math.Sin(1)); 
    }
    catch (Exception ex){
        Console.WriteLine(ex);
    }
}

MSIL:

.method private hidebysig static void  TestWithTryCatch() cil managed
{
  // Code size       49 (0x31)
  .maxstack  2
  .locals init ([0] class [mscorlib]System.Exception ex)
  IL_0000:  nop
  .try
  {
    IL_0001:  nop
    IL_0002:  ldstr      "SIN(1) = {0}"
    IL_0007:  ldc.r8     1.
    IL_0010:  call       float64 [mscorlib]System.Math::Sin(float64)
    IL_0015:  box        [mscorlib]System.Double
    IL_001a:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                  object)
    IL_001f:  nop
    IL_0020:  nop
    IL_0021:  leave.s    IL_002f //JUMP IF NO EXCEPTION
  }  // end .try
  catch [mscorlib]System.Exception 
  {
    IL_0023:  stloc.0
    IL_0024:  nop
    IL_0025:  ldloc.0
    IL_0026:  call       void [mscorlib]System.Console::WriteLine(object)
    IL_002b:  nop
    IL_002c:  nop
    IL_002d:  leave.s    IL_002f
  }  // end handler
  IL_002f:  nop
  IL_0030:  ret
} // end of method Program::TestWithTryCatch

I'm not an expert in IL but we can see that an local exception object is created on fourth line .locals init ([0] class [mscorlib]System.Exception ex) after that things are pretty same as for method without try/catch till the line seventeen IL_0021: leave.s IL_002f. If an exception occurs the control jumps to line IL_0025: ldloc.0 otherwise we jump to label IL_002d: leave.s IL_002f and function returns.

I can safely assume that if no exceptions occur then it is the overhead of creating local variables to hold exception objects only and a jump instruction.

TheVillageIdiot
  • 40,053
  • 20
  • 133
  • 188
  • 45
    Well, the IL includes a try/catch block in the same notation as in C#, so this does not really show how much overhead a try/catch means behind the scenes! Just that the IL does not add much more, does not mean the same as it is not added something in the compiled assembly code. The IL is just a common representation of all .NET languages. It is NOT machine code! – awe Sep 01 '09 at 08:06
81

No. If the trivial optimizations a try/finally block precludes actually have a measurable impact on your program, you probably should not be using .NET in the first place.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
  • 17
    That's an excellent point - compare to the other items on our list, this one should be minuscule. We should trust basic language features to behave correctly, and optimize what we can control (sql, indexes, algorithms). – Kobi Aug 22 '09 at 16:13
  • 8
    Think of tight loops mate. Example the loop where you read and de-serialize objects from a socket data stream in game server and your trying to squeeze as much as you can. So you MessagePack for object serialization instead of binaryformatter, and use ArrayPool instead of just creating byte arrays, etc... In these scenarios what is the impact of multiple (perhaps nested) try catch blocks within the tight loop. Some optimizations will be skipped by the compiler also exception variable goes to Gen0 GC. All I am saying is that there are "Some" scenarios where everything has an impact. – tcwicks May 21 '19 at 12:56
39

Quite comprehensive explanation of the .NET exception model.

Rico Mariani's Performance Tidbits: Exception Cost: When to throw and when not to

The first kind of cost is the static cost of having exception handling in your code at all. Managed exceptions actually do comparatively well here, by which I mean the static cost can be much lower than say in C++. Why is this? Well, static cost is really incurred in two kinds of places: First, the actual sites of try/finally/catch/throw where there's code for those constructs. Second, in unmanged code, there's the stealth cost associated with keeping track of all the objects that must be destructed in the event that an exception is thrown. There's a considerable amount of cleanup logic that must be present and the sneaky part is that even code that doesn't itself throw or catch or otherwise have any overt use of exceptions still bears the burden of knowing how to clean up after itself.

Dmitriy Zaslavskiy:

As per Chris Brumme's note: There is also a cost related to the fact the some optimization are not being performed by JIT in the presence of catch

Robert Cartaino
  • 27,494
  • 6
  • 45
  • 67
arul
  • 13,998
  • 1
  • 57
  • 77
  • 1
    Thing about C++ is that a very large chunk of the standard library will throw exceptions. There is nothing optional about them. You have to design your objects with some sort of exception policy, and once you've done that there is no more stealth cost. – David Thornley Aug 20 '09 at 20:22
  • Rico Mariani's claims are completely wrong for native C++. "the static cost can be much lower than say in C++" - This is simply not true. Although, I'm not sure what was the exception mechanism design in 2003 when the article was written. C++ really **has no cost at all** when exceptions are **not** thrown, no matter how many try/catch blocks you have and where they are. – BJovke Oct 31 '17 at 14:47
  • 1
    @BJovke C++ "zero cost exception handling" only means there's no run-time cost when exceptions are not thrown, but there's still a major code-size cost due to all the cleanup code calling destructors on exceptions. Also, while there isn't any exception-specific code being generated on the normal code path, the cost still isn't actually zero, because the possibility of exceptions still restricts the optimizer (e.g. stuff needed in case of an exception needs to stay around somewhere -> values can be discarded less aggressively -> less efficient register allocation) – Daniel Aug 02 '19 at 13:02
29

The structure is different in the example from Ben M. It will be extended overhead inside the inner for loop that will cause it to not be good comparison between the two cases.

The following is more accurate for comparison where the entire code to check (including variable declaration) is inside the Try/Catch block:

        for (int j = 0; j < 10; j++)
        {
            Stopwatch w = new Stopwatch();
            w.Start();
            try { 
                double d1 = 0; 
                for (int i = 0; i < 10000000; i++) { 
                    d1 = Math.Sin(d1);
                    d1 = Math.Sin(d1); 
                } 
            }
            catch (Exception ex) {
                Console.WriteLine(ex.ToString()); 
            }
            finally { 
                //d1 = Math.Sin(d1); 
            }
            w.Stop(); 
            Console.Write("   try/catch/finally: "); 
            Console.WriteLine(w.ElapsedMilliseconds); 
            w.Reset(); 
            w.Start(); 
            double d2 = 0; 
            for (int i = 0; i < 10000000; i++) { 
                d2 = Math.Sin(d2);
                d2 = Math.Sin(d2); 
            } 
            w.Stop(); 
            Console.Write("No try/catch/finally: "); 
            Console.WriteLine(w.ElapsedMilliseconds); 
            Console.WriteLine();
        }

When I ran the original test code from Ben M, I noticed a difference both in Debug and Releas configuration.

This version, I noticed a difference in the debug version (actually more than the other version), but it was no difference in the Release version.

Conclution:
Based on these test, I think we can say that Try/Catch does have a small impact on performance.

EDIT:
I tried to increase the loop value from 10000000 to 1000000000, and ran again in Release to get some differences in the release, and the result was this:

   try/catch/finally: 509
No try/catch/finally: 486

   try/catch/finally: 479
No try/catch/finally: 511

   try/catch/finally: 475
No try/catch/finally: 477

   try/catch/finally: 477
No try/catch/finally: 475

   try/catch/finally: 475
No try/catch/finally: 476

   try/catch/finally: 477
No try/catch/finally: 474

   try/catch/finally: 475
No try/catch/finally: 475

   try/catch/finally: 476
No try/catch/finally: 476

   try/catch/finally: 475
No try/catch/finally: 476

   try/catch/finally: 475
No try/catch/finally: 474

You see that the result is inconsequent. In some cases the version using Try/Catch is actually faster!

awe
  • 21,938
  • 6
  • 78
  • 91
  • 2
    I've noticed this too, sometimes it's faster with try/catch. I've commented it on Ben's answer. However, unlike 24 voters, I don't like this sort of benchmarking, I don't think it's a good indication. The code is faster in this case, but will it always be? – Kobi Aug 25 '09 at 09:44
  • 8
    Doesn't this prove that your machine was doing a variety of other tasks at the same time? Elapsed time is never a good measure, you need to use a profiler that records processor time, not elapsed time. – Colin Desmond Aug 25 '09 at 09:56
  • 2
    @Kobi: I aggree that this is not the best way to benchmark if you are going to publish it as a proof that your program runs faster than other or something, but can give you as a developer an indication of one method performing better than another. In this case, I think we can say that the differences (at least for Release configuration) is ignorable. – awe Aug 27 '09 at 06:05
  • 4
    You're not timing `try/catch` here. You're timing 12 try/catch _entering-critical-section_ against 10M loops. The noise of the loop will eradicate any influence the try/catch has. if instead you put the try/catch inside the tight loop, and compare with/without, you would end up with the cost of the try/catch. (no doubt, such coding is not good practice generally, but if you want to time the overhead of a construct, that's how you do it). Nowadays, BenchmarkDotNet is the go-to tool for reliable execution timings. – Abel Mar 31 '20 at 23:14
17

I tested the actual impact of a try..catch in a tight loop, and it's too small by itself to be a performance concern in any normal situation.

If the loop does very little work (in my test I did an x++), you can measure the impact of the exception handling. The loop with exception handling took about ten times longer to run.

If the loop does some actual work (in my test I called the Int32.Parse method), the exception handling has too little impact to be measurable. I got a much bigger difference by swapping the order of the loops...

Guffa
  • 687,336
  • 108
  • 737
  • 1,005
12

try catch blocks have a negligible impact on performance but exception Throwing can be pretty sizable, this is probably where your coworker was confused.

RHicke
  • 3,494
  • 3
  • 23
  • 23
12

Though "Prevention is better than handling", in the perspective of performance and efficiency we could chose the try-catch over the pre-varication. Consider the below code:

Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 1; i < int.MaxValue; i++)
{
    if (i != 0)
    {
        int k = 10 / i;
    }
}
stopwatch.Stop();
Console.WriteLine($"With Checking: {stopwatch.ElapsedMilliseconds}");
stopwatch.Reset();
stopwatch.Start();
for (int i = 1; i < int.MaxValue; i++)
{
    try
    {
        int k = 10 / i;
    }
    catch (Exception)
    {

    }
}
stopwatch.Stop();
Console.WriteLine($"With Exception: {stopwatch.ElapsedMilliseconds}");

Here is the result:

With Checking:  20367
With Exception: 13998
mayo
  • 3,845
  • 1
  • 32
  • 42
Teddy Oddman
  • 130
  • 1
  • 6
8

The try/catch HAS impact on the performance.

But its not a huge impact. try/catch complexity is generally O(1), just like a simple assignment, except when they are placed in a loop. So you have to use them wisely.

Here is a reference about try/catch performance (doesn't explain the complexity of it though, but it is implied). Take a look at Throw Fewer Exceptions section

Isaac
  • 2,332
  • 6
  • 33
  • 59
  • 4
    Complexity is O(1) it doesn't mean too much. For example if you equip a code section which is called very frequently with try-catch (or you mention a loop), the O(1)s could add up to a measurable number in the end. – Csaba Toth Aug 21 '13 at 17:37
7

In theory, a try/catch block will have no effect on code behavior unless an exception actually occurs. There are some rare circumstances, however, where the existence of a try/catch block may have a major effect, and some uncommon-but-hardly-obscure ones where the effect can be noticeable. The reason for this is that given code like:

Action q;
double thing1()
  { double total; for (int i=0; i<1000000; i++) total+=1.0/i; return total;}
double thing2()
  { q=null; return 1.0;}
...
x=thing1();     // statement1
x=thing2(x);    // statement2
doSomething(x); // statement3

the compiler may be able to optimize statement1 based upon the fact that statement2 is guaranteed to execute before statement3. If the compiler can recognize that thing1 has no side-effects and thing2 doesn't actually use x, it may safely omit thing1 altogether. If [as in this case] thing1 was expensive, that could be a major optimization, though the cases where thing1 is expensive are also those the compiler would be least likely to optimize out. Suppose the code were changed:

x=thing1();      // statement1
try
{ x=thing2(x); } // statement2
catch { q(); }
doSomething(x);  // statement3

Now there exists a sequence of events where statement3 could execute without statement2 having executed. Even if nothing in the code for thing2 could throw an exception, it would be possible that another thread could use an Interlocked.CompareExchange to notice that q was cleared and set it to Thread.ResetAbort, and then perform a Thread.Abort() before statement2 wrote its value to x. Then the catch would execute Thread.ResetAbort() [via delegate q], allowing execution to continue with statement3. Such a sequence of events would of course be exceptionally improbable, but a compiler is required to generate code which work according to specification even when such improbable events occur.

In general, the compiler is much more likely to notice opportunities to leave out simple bits of code than complex ones, and thus it would be rare for a try/catch could affect performance much if exceptions are never thrown. Still, there are some situations where the existence of a try/catch block may prevent optimizations which--but for the try/catch--would have allowed code to run faster.

supercat
  • 77,689
  • 9
  • 166
  • 211
7

Yes, try/catch will "hurt" performance (everything is relative). Not much in terms of wasted CPU cycles, but there are other important aspects to consider:

  • Code size
  • Method inlining

Benchmark

First, let's check the speed using some sophisticated tools (i.e. BenchmarkDotNet). Compiled as Release (AnyCPU), run on x64 machine. I would say there is no difference, even though the test will indeed tell us that NoTryCatch() is a tiny, tiny bit faster:

|            Method |   N |     Mean |     Error |    StdDev |
|------------------ |---- |---------:|----------:|----------:|
|        NoTryCatch | 0.5 | 3.770 ns | 0.0492 ns | 0.0411 ns |
|      WithTryCatch | 0.5 | 4.060 ns | 0.0410 ns | 0.0384 ns |
| WithTryCatchThrow | 0.5 | 3.924 ns | 0.0994 ns | 0.0881 ns |

Analysis

Some additional notes.

|            Method | Code size | Inlineable |
|------------------ |---------- |-----------:|
|        NoTryCatch |        12 |        yes |
|      WithTryCatch |        18 |          ? |
| WithTryCatchThrow |        18 |         no |

Code size NoTryCatch() yields 12 bytes in code whereas a try/catch adds another 6 bytes. Also, whenever writing a try/catch you will most likely have one or more throw new Exception("Message", ex) statements, further "bloating" the code.

The most important thing here though is code inlining. In .NET the mere existence of the throw keyword implies that the method will never be inlined by the compiler (implying slower code, but also less footprint). I recently tested this fact thoroughly, so it still seems valid in .NET Core. Not sure if try/catch follows the same rule. TODO: Verify!

Complete test code

using System;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

namespace TryCatchPerformance
{
    public class TryCatch
    {
        [Params(0.5)]
        public double N { get; set; }

        [Benchmark]
        public void NoTryCatch() => Math.Sin(N);

        [Benchmark]
        public void WithTryCatch()
        {
            try
            {
                Math.Sin(N);
            }
            catch
            {
            }
        }

        [Benchmark]
        public void WithTryCatchThrow()
        {
            try
            {
                Math.Sin(N);
            }
            catch (Exception ex)
            {
                throw;
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var summary = BenchmarkRunner.Run<TryCatch>();
        }
    }
}
l33t
  • 18,692
  • 16
  • 103
  • 180
5

See discussion on try/catch implementation for a discussion of how try/catch blocks work, and how some implementations have high overhead, and some have zero overhead, when no exceptions occur. In particular, I think the Windows 32 bit implementation has high overhead, and the 64 bit implementation does not.

Community
  • 1
  • 1
Ira Baxter
  • 93,541
  • 22
  • 172
  • 341
  • What I described are two different approaches to implementing exceptions. The approaches apply equally to C++ and C#, as well as managed/unmanaged code. Which ones MS chose for their C# I don't exactly know, but the exception-handling architecture of machine-level applications provided by MS uses the faster scheme. I'd be a bit surprised if the C# implementation for 64 bits didn't use it. – Ira Baxter Aug 21 '13 at 19:12
-1

I tested a deep try-catch.

        static void TryCatch(int level, int max)
        {
            try
            {
                if (level < max) TryCatch(level + 1, max);
            }
            catch
            { }
        }
        static void NoTryCatch(int level, int max)
        {
            if (level < max) NoTryCatch(level + 1, max);
        }
        static void Main(string[] args)
        {
            var s = new Stopwatch();
            const int max = 10000;
            s.Start();
            TryCatch(0, max);
            s.Stop();
            Console.WriteLine("try-catch " + s.Elapsed);
            s.Restart();
            NoTryCatch(0, max);
            s.Stop();
            Console.WriteLine("no try-catch " + s.Elapsed);
        }

The result:

try-catch 00:00:00.0008528
no try-catch 00:00:00.0002422
Snowy
  • 59
  • 8