Daily Archives: December 23, 2013

Exceptions (part 1, advanced)

Exceptions are not errors, they are exceptions. They should not be used for code that can deal with errors. Exceptions are for situations that cannot be solved like running out of RAM or hard disk space. They are pretty slow, because they deal with the entire stack trace. Here is a short benchmark program:

static double DoSomeCalc(double d) {
    return d * 1.1;
} //
static double DoSomeCalc2(Exception e) {
    throw e;
} //

static void Exceptions1() {
    const int n = 10000000;
    double d = 1.0;

    Stopwatch lStopwatch = new Stopwatch();
    lStopwatch.Start();

    for (int i = 0; i < n; i++) d *= 1.1;
    Console.WriteLine("benchmark ms " + lStopwatch.ElapsedMilliseconds);

    lStopwatch.Restart();
    try { for (int i = 0; i < n; i++)  d *= 1.1; }
    catch (Exception) { throw; }
    Console.WriteLine("efficient try/catch block ms " + lStopwatch.ElapsedMilliseconds);

    lStopwatch.Restart();
    for (int i = 0; i < n; i++) {
        try { d *= 1.1; }
        catch (Exception) { throw; }
    }
    Console.WriteLine("inefficient try/catch block ms " + lStopwatch.ElapsedMilliseconds);
    Console.WriteLine();

    lStopwatch.Restart();
    for (int i = 0; i < n; i++) d = DoSomeCalc(d);
    Console.WriteLine("method call, benchmark ms " + lStopwatch.ElapsedMilliseconds);

    lStopwatch.Restart();
    try { for (int i = 0; i < n; i++)  d = DoSomeCalc(d); }
    catch (Exception) { throw; }
    Console.WriteLine("method call, efficient try/catch block ms " + lStopwatch.ElapsedMilliseconds);

    lStopwatch.Restart();
    for (int i = 0; i < n; i++) {
        try { d = DoSomeCalc(d); }
        catch (Exception) { throw; }
    }
    Console.WriteLine("method call, inefficient try/catch block ms " + lStopwatch.ElapsedMilliseconds);
    Console.WriteLine();

    Exception e = new Exception();  // only one instance, we exclude the creation time for the object in this test
    lStopwatch.Restart();
    for (int i = 0; i < 100; i++) {
        try { throw e; }
        catch (Exception) { }
    }
    Console.WriteLine("100 exceptions thrown in ms " + lStopwatch.ElapsedMilliseconds);

    lStopwatch.Restart();
    for (int i = 0; i < 100; i++) {
        try { DoSomeCalc2(e); }
        catch (Exception) { }
    }
    Console.WriteLine("method call, 100 exceptions thrown in ms " + lStopwatch.ElapsedMilliseconds);

    lStopwatch.Stop();
    Console.ReadLine();
} //

example output:
benchmark ms 2227
efficient try/catch block ms 2179
inefficient try/catch block ms 2201

method call, benchmark ms 2448
method call, efficient try/catch block ms 2436
method call, inefficient try/catch block ms 2431

100 exceptions thrown in ms 603
method call, 100 exceptions thrown in ms 652

The first three results are in line with each other. The code optimizer does the job and there is no visible impact on the outcome. The difference is more likely to be a result of context switching (threading).
And when we call a method the slowdown is also regular, no big impact of the try/catch blog.
But when exceptions are thrown, the system considerably slows down. 600 ms for just 100 exceptions is a disaster. And it gets even worse when you call a method, because the stack trace becomes longer. Imagine what happens when you have nested methods involved.

The conclusion is that throwing exceptions for the programmer’s convenience is bad practice. Exceptions must be avoided by all means. You’d rather perform a quick zero check than raise a DivideByZeroException.

Do not reuse exception objects, this is not thread safe. In general try to avoid re-throwing exceptions. Anyway, let’s see how to re-throw exceptions in case you need it:

a) Re-“throw” without any identifier. This preserves the original exception details.

static void Exceptions2() {
    int lZero = 0;
    try { int i = 4 / lZero; }
    catch (Exception) { throw; }
} //

static void Exceptions3() {
    try { Exceptions2(); }
    catch (Exception e) {
        Console.WriteLine("Message:        {0}", e.Message);
        Console.WriteLine("StackTrace:     {0}", e.StackTrace);
        Console.WriteLine("HelpLink:       {0}", e.HelpLink);
        Console.WriteLine("InnerException: {0}", e.InnerException);
        Console.WriteLine("TargetSite:     {0}", e.TargetSite);
        Console.WriteLine("Source:         {0}", e.Source);
    }
} //

Message: Attempted to divide by zero.
StackTrace: at ConsoleApplication1.Program.Exceptions2() in ….\Program.cs:line 1155
at ConsoleApplication1.Program.Exceptions3() in ….\Program.cs:line 1160
HelpLink:
InnerException:
TargetSite: Void Exceptions2()
Source: ConsoleApplication1

b) Re-throw the original exception. Add some more information.

static void Exceptions4() {
    int lZero = 0;
    try { int i = 4 / lZero; }
    catch (DivideByZeroException e) { throw new DivideByZeroException("Division by Zero", e); }
    catch (Exception e) { throw new Exception("Any Exception", e); } // will not be thrown in this example
} //

static void Exceptions5() {
    try { Exceptions4(); }
    catch (Exception e) {
        Console.WriteLine("Message:        {0}", e.Message);
        Console.WriteLine("StackTrace:     {0}", e.StackTrace);
        Console.WriteLine("HelpLink:       {0}", e.HelpLink);
        Console.WriteLine("InnerException: {0}", e.InnerException);
        Console.WriteLine("TargetSite:     {0}", e.TargetSite);
        Console.WriteLine("Source:         {0}", e.Source);
    }
} //

example output:
Message: Division by Zero
StackTrace: at ConsoleApplication1.Program.Exceptions4() in ….\Program.cs:line 1134
at ConsoleApplication1.Program.Exceptions5() in ….\Program.cs:line 1140
HelpLink:
InnerException: System.DivideByZeroException: Attempted to divide by zero.
at ConsoleApplication1.Program.Exceptions4() in ….\Program.cs:line 1133
TargetSite: Void Exceptions4()
Source: ConsoleApplication1