2

I know the following catch and throw block is redundant, I am curious what kind of damage it can do ?

Can compiler optimizes it away in release mode? Or it will just catch the exception anyway and rethrow it ? If it is the latter case, what kind of performance penalty it will cause ?

try
{
  //...
}
catch {
  throw;
}
Jonathon Reinhart
  • 132,704
  • 33
  • 254
  • 328
nandin
  • 2,549
  • 5
  • 23
  • 27
  • 1
    I would compile an example, and look at the disassembly using [ILSpy](http://ilspy.net/) or another .NET disassembler. – Jonathon Reinhart Nov 12 '13 at 03:01
  • 1
    possible duplicate of [Can I remove empty catch with throw?](http://stackoverflow.com/questions/6517699/can-i-remove-empty-catch-with-throw) – inquisitive Nov 12 '13 at 03:03
  • 2
    I don't agree that this is a duplicate. The OP states that he already knows that the code is redundant and could be removed. Instead, he wishes to know what the actual impact is of leaving the code in place. – Jonathon Reinhart Nov 12 '13 at 03:37

1 Answers1

7

Optimization

The compiler will not optimize this out, even in a Release build.

Take the following test application:

public class Program {
    public static void Main(string[] args) {
        try {
            CallsThrow();
        }
        catch(Exception ex) {
            Console.WriteLine("Main() caught exception: " + ex.Message);
        }
        Console.Read();
    }

    private static void CallsThrow() {
        try {
            Throw();
        }
        catch {
            throw;
        }
    }

    private static void Throw() {
        throw new Exception("Here's my exception.");
    }
}

Using ILSpy we can look at the output binary at the IL level. We see that the try/catch in CallsThrow is still there in our Release binary:

.method private hidebysig static 
    void CallsThrow () cil managed 
{
    // Method begins at RVA 0x2094
    // Code size 11 (0xb)
    .maxstack 1

    .try
    {
        IL_0000: call void JunkCSharpConsole.Program::Throw()
        IL_0005: leave.s IL_000a
    } // end .try
    catch [mscorlib]System.Object
    {
        IL_0007: pop
        IL_0008: rethrow
    } // end handler

    IL_000a: ret
} // end of method Program::CallsThrow

Benchmark

Code:

public class Program
{
    public static void Main(string[] args) {
        const int N = 100000000;
#if DEBUG
        const string mode = "Debug";
#else
        const string mode = "Release";
#endif

        Console.WriteLine("Testing {0} iterations in {1} mode:", N, mode);

        // Attempt to JIT / cache
        CallsThrowWithTryCatch(false);
        CallsThrowWithoutTryCatch(false);

        // Test with try/catch+throw
        var s1 = Stopwatch.StartNew();
        for (int i = 0; i < N; i++ )
            CallsThrowWithTryCatch(false);
        s1.Stop();
        Console.WriteLine("  With try/catch: {0} ms", s1.ElapsedMilliseconds);

        // Test without try/catch+throw
        var s2 = Stopwatch.StartNew();
        for (int i = 0; i < N; i++)
            CallsThrowWithoutTryCatch(false);
        s2.Stop();
        Console.WriteLine("  Without try/catch: {0} ms", s2.ElapsedMilliseconds);

        var pct = (s1.ElapsedMilliseconds - s2.ElapsedMilliseconds) / (double)s1.ElapsedMilliseconds * 100.0;
        Console.WriteLine("No try/catch faster by {0:.02}%", pct);

        // Just show that it works
        try {
            CallsThrowWithTryCatch(true);
        }
        catch (Exception ex) {
            Console.WriteLine("Main() caught exception: " + ex.Message);
        }

        // Wait to exit
        Console.WriteLine("Press ENTER to exit.");
        Console.Read();
    }

    private static void CallsThrowWithTryCatch(bool doThrow) {
        try {
            Throw(doThrow);
        }
        catch {
            throw;
        }
    }

    private static void CallsThrowWithoutTryCatch(bool doThrow) {
        Throw(doThrow);
    }

    private static void Throw(bool doThrow) {
        if (doThrow)
            throw new Exception("Here's my exception.");
    }
}

Results:

Testing 100000000 iterations in Debug mode:
  With try/catch: 1492 ms
  Without try/catch: 1474 ms
No try/catch faster by 1.22%
Main() caught exception: Here's my exception.
Press ENTER to exit.

Testing 100000000 iterations in Release mode:
  With try/catch: 598 ms
  Without try/catch: 458 ms
No try/catch faster by 23.42%
Main() caught exception: Here's my exception.
Press ENTER to exit.

We can see that yes, there is a performance penalty associated with even the empty try/catch. In Debug builds it is not so significant, but a Release build showed a substantial 23.42% improvement by removing the try/catch.

Jonathon Reinhart
  • 132,704
  • 33
  • 254
  • 328
  • The overhead of one more throw would be minimal, second dumping the IL is a trivial exercise left to the reader, so where is the value add in answering a question like this at all? – Mike Beeler Nov 12 '13 at 03:33
  • 3
    The overhead is clearly not minimal. Did you look at the numbers? Also, it may be trivial to a more experienced developer, but I'm certain there is a large percentage of .NET developers that have no idea you can look at the generated binary at the IL level. – Jonathon Reinhart Nov 12 '13 at 03:34
  • Microsoft says handle only those exceptions that need to be handled either because an action needs to be performed or a fix up could take place, otherwise let the next level of exception handler deal with it, in this case it would be the last chance exception handler, functionally in that regard the code is the same as without a try catch block. Do I recommend it? No, are the performance implications disastrous No. Worse a throw at this point impedes debugging. – Mike Beeler Nov 12 '13 at 03:45
  • I completely understand the behavioral implications of including and excluding the handler in question. I believe the OP does as well. He asked about the low-level implications of including the handler, and I did my best to answer his specific questions. I think your concerns are going to go largely unheard here. – Jonathon Reinhart Nov 12 '13 at 03:51
  • I never questioned your understanding of the problem and as a measure of good faith up voted your answer just now. Largely other than providing a benchmark we agree more than not. Normally this forum requires that a questioner have a specific question and have made some attempt to resolve, this discussion is better off in programmer forum. – Mike Beeler Nov 12 '13 at 04:09
  • I disagree. Kicking this off to [programmers](http://programmers.stackexchange.com/) would be pointless, as it is about a specific piece of code, in a specific language. By your logic, we should remove every question tagged [`performance`](http://stackoverflow.com/questions/tagged/performance). I think "does a try/re-throw affect performance" is a perfectly valid question for Stack Overflow. – Jonathon Reinhart Nov 12 '13 at 04:12
  • ``[Stack Overflow is not a forum](http://meta.stackexchange.com/questions/92107/is-stack-overflow-a-forum/92110#92110)`` :-) – Jonathon Reinhart Nov 12 '13 at 04:13
  • 1
    actually the above empty `catch, throw` code was copied from a project written by our architect, I knew it stinks from the first impression, your answer helped me to understand why it smells. should I tell him or not, that is another question :) – nandin Nov 12 '13 at 20:38
  • @nandin Absolutely. Simply remove the pointless code, and include the URL to this question in the commit log. – Jonathon Reinhart Nov 14 '13 at 00:15