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?
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
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.
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.
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
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!
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.
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...
try catch blocks have a negligible impact on performance but exception Throwing can be pretty sizable, this is probably where your coworker was confused.
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
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
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.
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>();
}
}
}
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.
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
Success story sharing