A Word from the "Wise": Don't Use Exceptions
A while back, I went to a local .NET event which had a number of presentations given on a variety of topics. I attended an intermediate-level talk presented by an out-of-town MVP that was entitled "Advanced .Net Programming," or something like that. One of the sub-topics discussed was error handling, to which our MVP had some rather simple advice: don't throw exceptions. This seemed to be some rather peculiar advice, especially considering how exception handling was such an integral part of the .NET Framework, so I interrupted the speaker to ask for some clarification. He explained a bit further saying essentially that exceptions kill application performance and that you should use return codes because they are faster.
For most attendees, such reasoning would suffice. But not this one. No, it still didn't make sense. After all, I've used exceptions quite extensively to pass messages from the database all the way to the client, and I've never noticed a performance problem. Could it be that I perceive things in fast-motion? Maybe I'm oblivious to the fact that all of my applications are sluggish because an hour to me is what a minute it is to you?
Something's not right. Either our MVP cares a bit too much about speed or my perception of time is completely out of whack. What better than a few objective tests to figure this out? So I created a console app and started coding ...
First, I needed some code that did nothing. Well, not nothing, but nothing important.
Sub DoNothingImportant()
Dim x, y, z As Integer
y = 8432
x = 17751
z = (y + 112) * (x - 2040) / (Math.PI)
Dim s As String
s = "blah blah blooh"
s = s & "beep"
End Sub
This shouldn't take too long to run, right? Let's find out:
Console.WriteLine(Now().ToString("hh:mm:ss.fffffff"))
DoNothingImportant()
Console.WriteLine(Now().ToString("hh:mm:ss.fffffff"))Output:
10:35:33.5930062
10:35:33.5930062
Sheesh, it goes so fast I can't even measure it. For fun, I thought I'd see how fast I could do the simple arithmetic in DoNothingImportant(). Unfortunately, manual decimal long division is not like riding a bike. As it turns out, I have no idea how to go about dividing 3.1428 into 134,234,784 by hand. I'm ashamed and I'm embarrassed. For this very reason, I have decided not share with you how long the multiplication portion took me. Did I mention I have a minor in mathematics?
To make myself feel a little better, let's watch the computer choke on doing this arithmetic 100 times in a row!
For i As Integer = 1 To 100Console.WriteLine(Now().ToString("hh:mm:ss.fffffff"))
DoNothingImportant()
Next
Console.WriteLine(Now().ToString("hh:mm:ss.fffffff"))
Output:
10:37:05.5096764
10:37:05.5096764
Damn you gigahertz! Back to the topic at hand though. Let's go kill our performance with exceptions:
Sub ThrowException()
Try
Throw New Exception
Catch ex As Exception
Finally
End Try
End Sub
Console.WriteLine(Now().ToString("hh:mm:ss.fffffff"))
ThrowException()
DoNothingImportant()
Console.WriteLine(Now().ToString("hh:mm:ss.fffffff"))
Output:
10:40:18.5456990
10:40:18.5456990
Hmm. Weird. It looked instant to me and the computer. Ok, how about if we throw 10 exceptions.
For i As Integer = 1 To 10Console.WriteLine(Now().ToString("hh:mm:ss.fffffff"))
ThrowException()
DoNothingImportant()
Next
Console.WriteLine(Now().ToString("hh:mm:ss.fffffff"))
Output:
10:46:11.7123974
10:46:11.7123974
Sheesh. This computer is frickin amazing. Ok, maybe it's not exceptions that kill performance, but nested exceptions. Let's find out:
Sub ThrowRecursiveExceptions(ByVal count As Integer)
If count < 1 Then Return
Try
Throw New Exception
Catch ex As Exception
ThrowRecursiveExceptions(count - 1)
Finally
End Try
End Sub
Console.WriteLine(Now().ToString("hh:mm:ss.fffffff"))
DoNothingImportant()
ThrowRecursiveExceptions(10)
Console.WriteLine(Now().ToString("hh:mm:ss.fffffff"))
Output:
10:48:45.8648346
10:48:45.8648346
Ok this is getting ridiculous. I was told by an expert that exceptions would kill performance. What gives? Let's increase the magnitude:
Console.WriteLine(Now().ToString("hh:mm:ss.fffffff"))
For i As Integer = 1 To 100
ThrowException()
DoNothingImportant()
Next
Console.WriteLine(Now().ToString("hh:mm:ss.fffffff"))Output:
10:51:56.5876694
10:51:56.6377384Console.WriteLine(Now().ToString("hh:mm:ss.fffffff"))
DoNothingImportant()
ThrowRecursiveExceptions(100)
Console.WriteLine(Now().ToString("hh:mm:ss.fffffff"))Output:
10:52:34.6477522
10:52:35.2385664
Finally! A measurable difference! Granted, it's still only only measured in hundredths and tenths of a second respectably, but this is progress. Let's kick it up a notch:
Console.WriteLine(Now().ToString("hh:mm:ss.fffffff"))
For i As Integer = 1 To 1000
ThrowException()
DoNothingImportant()
Next
Console.WriteLine(Now().ToString("hh:mm:ss.fffffff"))Output:
10:55:17.5446078
10:55:18.0152564Console.WriteLine(Now().ToString("hh:mm:ss.fffffff"))
DoNothingImportant()
ThrowRecursiveExceptions(1000)
Console.WriteLine(Now().ToString("hh:mm:ss.fffffff"))Output:
10:57:38.0252702
10:58:39.9205680
Ok. Throwing 1000 nested exceptions takes a long frickin time. Maybe this is what our MVP was talking about?
Now, let's review what we learned:
- Modern computers are fast. Really fast. Really, really, really, really, really fast.
- Long division is hard. Really hard.
- Throwing one exception won't affect performance.
- Throwing ten exceptions (nested or otherwise) won't affect performance.
- Throwing one hundred exceptions (nested or otherwise) probably won't affect performance.
- Throwing one thousand nested exceptions will most definitely cause your application to perform slowly.
- The call stack actually supports 1000 levels of recursion
- Some people don't believe Lessons #1, #3, and #4.
- An individual's Title does not automatically mean they have any clue what they're talking about.
- If some one ever says "because it's faster," think of Lesson #1 and #9 and laugh.
Note: I "primed" each method before running it in order to not have JIT compilation included in the time tests.