Try/Catch Performance

I'm really not obsessed with performance -- honest!

However, when a co-worker asked me today exception handling was an acceptable way of coding defensively, my reaction was rather predictable. Exceptions are pure evil, and should be... well, exceptional.

Yes, you guessed it. The next question was "How bad is try/catch really?"

The short answer is that is involves minimal overhead... unless an exception is thrown. In that case, the .NET exception handling mechanism does a few nice things, like providing the stack trace as part of the exception. Great for debugging... lousy for performance. Of course, if exceptions are treated as being exceptional -- that is, they're only raised when something you haven't predicted occurs -- then you really shouldn't care about performance. Since your application is most likely left in an unknown or unstable state after an exception, the odds are you're going to reset a substantial part of your application, if not shut it down entirely.

As a rule, you can predict that users will provide data that is, to put it politely, suspect. This isn't a reflection on the users; if anything, it's more a comment on the tendency of developers to know how their own code works, and to continually feed it good data. But, both for this reason, and for security reasons, the ASP.NET mantra of "never trust any input" applies in pretty much any application. Code defensively against user input, so that you don't have to rely on try/catch blocks to deal with exceptions.

How significant is the overhead of catching an exception? Here's the code for a console application to illustrate:

1: Module Module1

2: Sub Main()

3: TestNaive(1)

4: TestDefensive(1)

5: TestWithTryCatch(1)

6: TestWithException(1)

7:

8: Dim maxCount As Integer = 1000000

9:

10: Console.WriteLine("Naive: " & TestNaive(maxCount) & " ticks")

11: Console.WriteLine("Defensive coding, no try/catch: " & TestDefensive(maxCount) & " ticks")

12: Console.WriteLine("Naive with try/catch: " & TestWithTryCatch(maxCount) & " ticks")

13: Console.WriteLine("Exception: " & TestWithException(maxCount) & " ticks")

14: Console.ReadLine()

15: End Sub

16:

17: Private Function TestNaive(ByVal iterationCount As Integer) As Double

18: Dim sw As New Stopwatch

19: sw.Start()

20: For i As Integer = 1 To iterationCount

21: Dim x As Double = i / (iterationCount - i + 1)

22: Next

23: sw.Stop()

24:

25: Return sw.ElapsedTicks / iterationCount

26: End Function

27:

28: Private Function TestDefensive(ByVal iterationCount As Integer) As Double

29: Dim sw As New Stopwatch

30: sw.Start()

31: For i As Integer = 1 To iterationCount

32: Dim y As Integer = (iterationCount - i + 1)

33: If Not y = 0 Then

34: Dim x As Double = i / y

35: End If

36: Next

37: sw.Stop()

38:

39: Return sw.ElapsedTicks / iterationCount

40: End Function

41:

42: Private Function TestWithTryCatch(ByVal iterationCount As Integer) As Double

43: Dim sw As New Stopwatch

44: sw.Start()

45: For i As Integer = 1 To iterationCount

46: Try

47: Dim x As Double = i / (iterationCount - i + 1)

48: Catch ex As Exception

49:

50: End Try

51: Next

52: sw.Stop()

53:

54: Return sw.ElapsedTicks / iterationCount

55: End Function

56:

57: Private Function TestWithException(ByVal iterationCount As Integer) As Double

58: Dim sw As New Stopwatch

59: sw.Start()

60: For i As Integer = 1 To iterationCount

61: Try

62: Dim x As Double = i / 0

63: Catch ex As Exception

64:

65: End Try

66: Next

67: sw.Stop()

68:

69: Return sw.ElapsedTicks / iterationCount

70: End Function

71: End Module

My results tend to vary a little bit, since the work done in each loop so trivial that it is susceptible to noise. The first three tests are generally pretty close to each other. The fourth test -- testing with an exception raised -- is always much longer. Generally it's somewhat more than 5x longer than the other tests.

TryCatchPerfTest

TryCatchPerfTest2

What I find really interesting is that the inclusion of the try/catch block appears to make the naive implementation run faster. I don't have the .NET debugging symbols loaded on my machine, and dotTrace really didn't provide any insight. Anyone out there who has dug deeply into the CLR and can provide insight, it would be much appreciated.

However, the moral of the story is straight-forward. Treat exceptions as exceptional. Code defensively rather than catching exceptions wherever possible. No surprise there.

This article is part of the GWB Archives. Original Author: Jeff Certain

New on Geeks with Blogs