Share via


Exception Cost: When to throw and when not to

The Cost of Exceptions

I wish I could speak intelligently on the exact cost but it's really quite difficult to project for any given usage, it's best measured for your specific cases.  However there are a couple of different kinds of cost and they're both worth at least a few words.

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.

In the managed world, there isn't this widespread notion of deterministic destruction of objects.  This means that the static cost of exceptions can be quite a bit lower, because just creating new objects doesn't force you to update your exception state.  Objects that become dead as a result of a thrown exception are handled no differently than other dead objects, they don't need special tables.  So the static cost is really driven by overt use of exception features, and not so much implicit cleanup.

The second kind of cost is associated with actually throwing an exception.  This is where the cost is higher in the managed world -- indeed it really couldn't possibly be lower because the same unmanaged exception throwing mechanism is used to actually get the exception started so we began at the unmanaged baseline.  Where the extra cost comes in is the richness of features that are available in managed exceptions -- things like having the full call stack available in the exception object for display/interrogation.  Those features cost space/time, and while we believe they offer an excellent deal in terms of increasing the ability to diagnose and correct exceptions you still need to be aware that the throw is not particularly cheap.

I have two “almost rules” for throwing exceptions, and a little bit of advice about exceptions in general.  As usual my “almost rules” have exceptions and I leave it to the reader to identify them :) 

Almost Rule #1

When deciding if you should throw an exception, pretend that the throw statement makes the computer beep 3 times, and sleep for 2 seconds.  If you still want to throw under those circumstances, go for it.

Seriously, people throw much too often, and for things that aren't really very exceptional at all.  In fact, my own opinion is that the framework itself throws far more often than it should.  The actual throwing of exceptions is sufficiently costly that I try to limit it to cases where I'm certain that less than 1 call in 1000 (99.9%) to a particular service will require an exception.  This means throwing exceptions on things like invalid arguments to an API is probably just fine, but on the other hand throwing an exception due to invalid user input, or badly formatted text from an external system, could be a bad idea.  Significant use of exceptions in business logic validation is more often a bad idea than a good one, so be careful out there.

Almost Rule #2

If you think it will be at all normal for anyone to want to catch your exception, then probably it shouldn't be an exception at all.

Again, my favorite consumer of exceptions is a generic exception handler at a fairly high level on the stack.  Anything that fundamentally needs to be dealt with at a lower level should be looked at suspiciously.  Often such a beast would fail the “only 1/1000” of the time test.  Since doing control flow with exceptions is alot more expensive/confusing than other forms of control flow its best avoided if the special handling is commonplace.

What I like to see is general purpose logic for state cleanup, transaction abort, reset and retry.  What I don't like to see is “downgrading” an exception into a return code, or routinely being able to “ignore” the exception and proceed.  Those latter cases are usually a bad sign.

Following these Almost Rules whenever possible will help keep you on the straight and narrow.

Hidden Exception Usage

Last but not least, don't forget that certain standard language features include try/finally implicitly.  Both the “using” and “foreach” statement can require a try/finally in order to give the necessary semantics.  Here's an interesting blog from Sudhakar Sadasivuni which discusses this for the using case.  Foreach does a similar thing, as Mark Michaelis discusses.

Comments

  • Anonymous
    December 19, 2003
    You said:
    In the managed world, there isn't this
    widespread notion of deterministic
    destruction of objects. This means that
    the static cost of exceptions can be quite
    a bit lower,

    Seeing that the new Managed C++/CLI coming in Whidbey will have deterministic finalization, will it return to the same level of cost as standard C++?

  • Anonymous
    December 19, 2003
    The comment has been removed

  • Anonymous
    December 19, 2003
    Yes, deterministic destruction would increase the cost to the standard level. Naturally we're always trying to find clever ways to keep that to a minimum but even low(er) cost is more than none at all.

  • Anonymous
    December 19, 2003
    The comment has been removed

  • Anonymous
    December 19, 2003
    Sadly, I have a very long list of people to speak to...

  • Anonymous
    December 19, 2003
    The hidden exception usage case is a little misleading. When using try-finally constructs and flow control leaves the try block normally (i.e. no exception occurred) then you do not incur the overhead of the SEH stack walk that you would get had an exception been thrown and the finally block was executed on the 2nd pass as the stack was being unwound via a global unwind. There may be some overhead if bookkeeping is done to tables tracking the state of objects within the body of the method.

    I'd be interested in how much overhead there is in a try-finally where an exception does not occur. It should be very low.

  • Anonymous
    December 19, 2003
    You are correct, in the normal case with the finally block and no exception all that has to happen is a fairly minor state change to indicate that the finally block need not be run. However it's not nothing and such state changes do add up.

    In unmananged code you could think of each destructor as being in its own implicit finally block and you wouldn't be too far from the truth.

    The fact that these implicit finally blocks can be absent in the managed world helps reduce the static cost.

  • Anonymous
    December 19, 2003
    By the way, I'd just like to say how much I appreciate the thoughtful comments and clarifications that my readers are making.

    Thank you very much guys, it really helps the overall quality of the blog a lot. Even if it does mean a little egg on the face for me sometimes :)

    Greater good and all that :)

  • Anonymous
    December 19, 2003
    We all appreciate the quality of your blog. That's why you're the #4 hit on Google for 'rico'.

  • Anonymous
    December 20, 2003
    We have been hit by exception overhead in a couple MS APIs.

    System.Drawing routinely throws exceptions, which are often impossible to predict. Put a few hundred of these in a tight display loop... it becomes horrible. One example, is an extreme zoomout matrix, when displaying text. Throws an exception, but to predict when? Nearly impossible.

    The DTE api (the VS automation object model) usually throws an exception to indicate an object cannot be found in a collection. Simply looking up a control on a command bar can throw an exception. This has also caused perf hits for us.

    Thanks for the focus on this widespread problem! I hope it will lead to some changes in the MS libraries.

  • Anonymous
    December 22, 2003
    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 presents of catch

  • Anonymous
    January 05, 2004
    The comment has been removed

  • Anonymous
    May 26, 2004
    I guess myself the reason of such a big cost when throwing an exception.

  • Anonymous
    September 14, 2006
    Here's an article under Jon's Homepage that was just brought to my attention.  It's an interesting...

  • Anonymous
    October 04, 2006
    Prashant Bansode, Bhavin Raichura, and Girisha Gadikere teamed up with Claudio Caldato (CLR team) to

  • Anonymous
    January 01, 2008
    PingBack from http://imehta.com/blog-ketan/?p=15

  • Anonymous
    March 24, 2009
    Exceptions (unexpected errors) are a part of every application lifecycle. No matter how reliable your