Source of Article : http://blogs.msdn.com/b/chris.green/archive/2008/02/11/don-t-use-exceptions-to-control-application-flow.aspx
Why? Because they can be thousands of times slower
than method return values!
Can you guess why?
First here's an example of what not to do. (from
the Patterns & Practices Guidance article)
static void
ProductExists( string ProductId)
{
//... search for Product
if ( dr.Read(ProductId) ==0 ) // no record found, ask to create
{
throw( new
Exception("Product Not found"));
}
}
This should have been handled with a meaningful method
return value (such as 'null' or '-1' for example) and is a bad
idea for couple of reasons:
1.
Not being
able to find a product is not an exceptional circumstance. The program logic
should be able to handle this with a method return value rather than an
Exception. I’m sure you can think of other similar cases where Exceptions have
been used inappropriately.
2.
Exceptions
are excellent when used correctly but they come with a price - execution
overhead. The CLR is much, much slower to process Exceptions than it is to
process normal program flow and method return values.
What actually happens when an Exception is
thrown?(from the 'Exceptions Overview' section of the
.NET Framework Developer's Guide)
"When an exception
occurs, the runtime begins a two-step process:
1. The runtime searches the array for the first protected block
that:
· Protects a region that
includes the currently executing instruction, and
· Contains an exception
handler or contains a filter that handles the exception.
2. If a match occurs, the runtime creates an Exception
object that describes the exception. The runtime then executes all finally or
fault statements between the statement where the exception occurred and the
statement handling the exception. Note that the order of exception handlers is
important: the innermost exception handler is evaluated first. Also note that
exception handlers can access the local variables and local memory of the
routine that catches the exception, but any intermediate values at the time the
exception is thrown are lost.
If no match occurs in
the current method, the runtime searches each caller of the current method, and
it continues this path all the way up the stack. If no caller has a match, the
runtime allows the debugger to access the exception. If the debugger does not
attach to the exception, the runtime raises the UnhandledException event. If there are no
listeners for the UnhandledException event, the runtime dumps a stack
trace and ends the program."
That’s alot of non-program related processing going
on. You can imagine how convoluted that can get with a deep call stack and
nested Exceptions.
I was curious as to the real effect of all this
processing compared to a plain old return value so I whipped up some
embarrassingly crummy code to test it (code included below). I was astonished
at the results! Here’s a copy of the results of a test run on my dual core
2.2GHz 4GB T61p Lenovo Thinkpad running Visual Studio 2008.
Return value elapsed
time = 10 uSec
Return value elapsed time = 9 uSec
Return value elapsed time = 8 uSec
Exception handling elapsed time = 2104 mSec
Exception handling elapsed time = 2066 mSec
Exception handling elapsed time = 2078 mSec
On average return values
was 231407 times faster than exception handling.
Return value elapsed time = 8 uSec
Return value elapsed time = 8 uSec
Return value elapsed time = 8 uSec
Exception handling elapsed time = 2084 mSec
Exception handling elapsed time = 2099 mSec
Exception handling elapsed time = 2100 mSec
On average return values
was 245705 times faster than exception handling.
Completed 2 timing tests. Press any key to exit...
What a huge difference in performance! A few things to
note about the output shown here:
1.
There are two timing test runs. Two is an arbitrary number. I just wanted to
show that it varies slightly between runs.
2.
Each run is comprised of three return value test runs and three exception
handler test runs. I did this to allow the execution environment to stabliize
. You can see the slight bump in the first set of numbers.
3.
I do a quick calculation at the end of each run to estimate the overall
performance difference and what a difference there is - 8 microseconds vs
2,100,000 microseconds.
Then I realised I had compiled in Debug mode. “Of
course it’s going to be slow” I thought to myself. So I recompiled and ran it
in Release mode ... the difference was even greater!
Return value elapsed
time = 6 uSec
Return value elapsed time = 5 uSec
Return value elapsed time = 4 uSec
Exception handling elapsed time = 2184 mSec
Exception handling elapsed time = 2142 mSec
Exception handling elapsed time = 2167 mSec
On average return values
was 432866 times faster than exception handling.
Return value elapsed time = 4 uSec
Return value elapsed time = 4 uSec
Return value elapsed time = 4 uSec
Exception handling elapsed time = 2506 mSec
Exception handling elapsed time = 2175 mSec
Exception handling elapsed time = 2156 mSec
On average return values
was 493703 times faster than exception handling.
Completed 2 timing tests. Press any key to exit...
Notice the return value timings are roughly half what
they were in Debug mode. My guess is that because Debug code is full of MSIL
‘NOP’ (no-op) instructions (so break points can be inserted if needed), it
takes longer to process the MSIL where every second instruction is a NOP. Maybe
that slows the “return values” timing test down in Debug mode. It doesn’t seem
to make much difference to the Exception processing time though.
So it’s clear to me: (A
“Note to self” that I thought I’d share with you)
“Exceptions are an
exceptionally J good tool for dealing with exceptional
circumstances at runtime but should rarely, if ever, be used for routine
program flow control.”
Anyway, I found this little excursion into the land of
Exception processing performance enlightening - I hope you did too.
Here's the C# code I used...
using System;
namespace ExceptionsVsreturnValues
{
class Program
{
static int
itterations = 1000;
static int
nStabilizeLoops = 3;
static int
nTestLoops = 2;
static DateTime
start, end;
static TimeSpan
retElapsed, retElapsedTotal, exceptionElapsed, exceptionElapsedTotal;
static void
Main(string[] args)
{
for (int i = 0;
i < nTestLoops; i++)
{
// First the return value timing
returnValueTimingTest();
// Next the exception handling timing
exceptionHandlingTimingtest();
// Report the results
Console.WriteLine("\nOn
average return values was " +
(int)(exceptionElapsedTotal.TotalMilliseconds /
retElapsedTotal.TotalMilliseconds * 1000) +
" times faster than exception handling.");
}
Console.Write("Completed
" + nTestLoops + " timing tests.
Press any key to exit...");
Console.ReadKey(false);
}
private static void exceptionHandlingTimingtest()
{
// Run the test 'nLoop' times to allow the executio
nenvironment to stabilise
for (int i = 0;
i < nStabilizeLoops; i++)
{
start = DateTime.Now;
for (int j = 0;
j < itterations; j++)
{
try
{
// Deliberatley send a null in place of the expected
string
// to cause a null pointer exception to be thrown
findCustomerWithExceptions(null);
}
catch //(Exception e)
{
// Uncomment this and above if you want proof that an
exception is being thrown
// Console.WriteLine("Exception \"" +
e.Message + "\" caught");
}
}
end = DateTime.Now;
exceptionElapsed = end - start;
if (exceptionElapsedTotal == null) exceptionElapsedTotal = exceptionElapsed;
else
exceptionElapsedTotal += exceptionElapsed;
Console.WriteLine("Exception
handling elapsed time = " +
exceptionElapsed.TotalMilliseconds + "
mSec");
}
}
private static void returnValueTimingTest()
{
// Run the test 'nLoop' times to allow the executio
nenvironment to stabilise
for (int i = 0;
i < nStabilizeLoops; i++)
{
start = DateTime.Now;
for (int j = 0;
j < itterations * 1000 /* need to scale up the
time from uSec to mSec because this is too fast for raw comparison! */;
j++)
{
findCustomer("fred");
}
end = DateTime.Now;
retElapsed = end - start;
if (retElapsedTotal == null)
retElapsedTotal = retElapsed;
else retElapsedTotal += retElapsed;
Console.WriteLine("Return
value elapsed time = " + retElapsed.TotalMilliseconds + " uSec");
}
}
private static void findCustomerWithExceptions(string p)
{
// causes a null pointer exception if p == null
p.Trim();
}
private static int findCustomer(string
p)
{
return 0;
}
}
}
P.S. The story is similar with Java. I did a quick port to JDK 6 Update 4
and found return values are in the order of 4,000 times faster than Exception
handling in the Sun JVM. Not as wide a gap as the CLR but still enough to avoid
using Exceptions inappropriately no matter what platform you choose.