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!

Console.WriteLine(Now().ToString("hh:mm:ss.fffffff"))
For i As Integer = 1 To 100
 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.

Console.WriteLine(Now().ToString("hh:mm:ss.fffffff"))
For i As Integer = 1 To 10
 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.6377384

Console.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.0152564

Console.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:

  1. Modern computers are fast. Really fast. Really, really, really, really, really fast.
  2. Long division is hard. Really hard.
  3. Throwing one exception won't affect performance.
  4. Throwing ten exceptions (nested or otherwise) won't affect performance.
  5. Throwing one hundred exceptions (nested or otherwise) probably won't affect performance.
  6. Throwing one thousand nested exceptions will most definitely cause your application to perform slowly.
  7. The call stack actually supports 1000 levels of recursion
  8. Some people don't believe Lessons #1, #3, and #4.
  9. An individual's Title does not automatically mean they have any clue what they're talking about.
  10. 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.

Published Tuesday, March 29, 2005 11:12 AM by Alex Papadimoulis

Comments

Tuesday, March 29, 2005 11:29 AM by Jim Arnold

# re: A Word from the "Wise": Don't Use Exceptions

+1

I've seen this nonsense lots of times, and the really stupid thing is: even if exceptions *are* slow, why are you concerned about the performance of your application when it CLEARLY DOESN'T WORK PROPERLY?

Jim
Tuesday, March 29, 2005 11:37 AM by Vurg

# re: A Word from the "Wise": Don't Use Exceptions

That's just weird. That person is probably using exceptions the wrong way. To think about it, exceptions indicate a potential error condition that should be handled separately from the main application logic, and it doesn't make sense to perform speed tests.
Tuesday, March 29, 2005 11:44 AM by Scott Elkin

# re: A Word from the "Wise": Don't Use Exceptions

That's really funny.
Tuesday, March 29, 2005 3:01 PM by Richard P

# re: A Word from the "Wise": Don't Use Exceptions

I think everyone here so far gets it.

Exceptions are for exceptional circumstances.

A user's search returning 0 results is quite common and therefore *not* exceptional.

The network connection being dropped mid-way through a file transfer is exceptional.

Now, exceptions *can* be expensive from a performance standpoint in complex systems because of automatic exception handling and reporting. A persistant server application has to recover from exceptions and that often means a whole lot of flushing state to get back to a known safe state. But that's OK. Because you log your exceptions, you fix the bugs, and then they don't happen ever again. RIGHT!?!?
Tuesday, March 29, 2005 3:28 PM by Douglas Reilly

# re: A Word from the "Wise": Don't Use Exceptions

What Richard P said. Exceptions should not be used for normal processing, but for, well, "exceptions", they are the perfect thing to use.
Tuesday, March 29, 2005 4:02 PM by Callum

# re: A Word from the "Wise": Don't Use Exceptions

They give MVPs out in packets of cereal.
Tuesday, March 29, 2005 10:30 PM by Wallym

# re: A Word from the "Wise": Don't Use Exceptions

Well, I'm an MVP and I didn't get it through a packet of cereal............ ;-)

Well, I throw exceptions when bad things happen, and I plan on continueing to do so.......hmmmm
Tuesday, March 29, 2005 11:41 PM by Jon Limjap

# re: A Word from the "Wise": Don't Use Exceptions

Makes sense. It's called an exception because either something bad or something unexpected happens.

For an MVP to say *not* to throw exceptions, maybe he meant not to throw your own exceptions?

Oh whatever. I'm happy with my "Catch ex"s
Wednesday, March 30, 2005 12:04 AM by foobar

# re: A Word from the "Wise": Don't Use Exceptions

Back in the evil pre-managed C++ days, when exceptions were first added as a feature, they did slow down performance quite a bit.

Of course, technology advances and those bad days are long gone.
Wednesday, March 30, 2005 4:47 AM by Andrew Armstrong

# re: A Word from the "Wise": Don't Use Exceptions

I agree with the authors comments. However, care must be taken regarding when to catch and throw exceptions. .NET does not mandate trapped exception (unlike Java) - this is good, it allows the developer to pick and choose what exceptions are important. However, I have seen lots of code that catches exceptions then just throws them again - this is dumb! Exceptions should only be caught when you intend to do something useful with them, otherwise just leave them be and let the top-level application deal with them after bubbling....
So to choose to ignore exception would seem foolish to me, let's just all agree to use them properly and let your super-quick processors deal with the mimimal overhead.

Andrew, (non-MVP - is that bad?)
Wednesday, March 30, 2005 8:45 PM by JosephCooney

# re: A Word from the "Wise": Don't Use Exceptions

I agree with what Oleg said. If you look at what happens when an exception is thrown it is significant. Reading about the internals of the SSCLI implementation in the SSCLI Essentials book from O'Reilly was enough to foreswear me from throwing them ever un-necessarily. Maybe my PC is not fast enough but I "notice" when an exception is trown by the lag it introduces before any message appears, and I certainly wouldn't be using them as part of routine message passing. They are for "exceptional" circumstances.
Wednesday, March 30, 2005 11:45 PM by Jon Limjap

# re: A Word from the "Wise": Don't Use Exceptions

Oleg,

We want numbers, like the way Alex did it.

Even if the computer/server/whatever does just that, how long does it really take anyway?

Thursday, March 31, 2005 6:29 AM by Marcel Popescu

# re: A Word from the "Wise": Don't Use Exceptions

<<Generally speaking it is better to not use exceptions. Especially when talking about businesslogic.>>

Ramon, that was a joke, right? (I can never tell.) Especially when it comes to business logic, you don't want a lazy programmer to forget to check a return code. You REALLY don't want to accept an order for 300 screws when you only have 200 in stock, because someone, somewhere, didn't check a return code. Speed be damned - the integrity of the data comes first. (And, if you still worry about speed, you really didn't pay attention to Alex' #1 conclusion.)
Wednesday, April 20, 2005 7:28 PM by Bobstar

# re: A Word from the "Wise": Don't Use Exceptions

Yeah thats funny, i've always been told not to use exceptions.. Well atleast only in special circumstances. Anyway...

As Oleg Tkachenko writes <<Oh boy! Using exceptions to pass messages between tiers ??????? That's crazy. You better never show that code to anybody>>
And why is that so?, imho thats exactly one of the places that an exception fullfills it's usefullness.
Monday, April 23, 2007 10:03 AM by Francois Beaussier

# re: A Word from the "Wise": Don't Use Exceptions

On my dual core, throwing and catching an exception takes on average about 6ms. This is the simplified version of the code I used:

private void Execute()

{

   Stopwatch sw = new Stopwatch();

   sw.Start();

   ThrowAndCatchException();

   sw.Stop();

   Console.WriteLine("Time taken: {0}ms", sw.ElapsedMilliseconds);

}

private void ThrowAndCatchException()

{

   try

   {

       throw new Exception();

   }

   catch (Exception) { }

   {

   }

}

6ms is significant enough, isn't it ?