Squak !
18Apr/10

Measuring the Overhead of Exceptions

Someone mentioned to me lately that I shouldn't use exceptions in C# to control flow, because it imposed a lot of overhead. I thought about this for a minute and I realized that it is probably true, but to what extent ? I had no idea. So, I decided to measure it.

There are many possible cases for this. I'll start by discussing one of the simplest: arithmetic overflow. Arithmetic overflow happens when the result of a calculation does not fit in the storage space reserved for the result. A simple example is: add one to the maximum integer value, like this:

try {
  int a;
  a = int.MaxValue;
  a = a + 1;
} catch (Exception) {
  throw;
}

Except, that doesn't throw an exception ... huh ?

It turns out that Visual Studio's default settings for the C# compiler ignores this case. So, I went to Project / Properties / Advanced and checked the "check for arithmetic underflow/overflow" box.

Now, the exception is thrown.

A couple of more lines of code, and I had a usable test case that could measure the difference, and the difference was quite a lot. Here are screen shots of some test runs:


That's 5 to 6 orders of magnitude difference!

Here's the code. Comments and criticisms are welcome at comments at squakmt dot com .

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;

namespace ExceptionPerformanceTest {
  public partial class Form1 : Form {
    public Form1() {
      InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e) {
    }

    private void _run_test_Click(object sender, EventArgs e) {
      _exception_test_1();
    }

    private void _exception_test_1() {
      // exception performance comparison
      // test1:
      // Roll int from intmax to zero.
      // 1. by pretest, 2. by exception

      // Clear any prior test results
      foreach ( Control ctl in this.Controls ){
        if ( ctl is TextBox ){
          ctl.Text = String.Empty;
          ctl.Update();
        }
      }

      //
      // Pretest method
      //
      var a = new int();
      a = int.MaxValue;
      int p = 0;
      int x = 0;
      int cycles = 1000;

      var _swp = new Stopwatch();
      _swp.Start();
      for (p = 0 ; p < cycles ; p++) {
        var b = _roll_to_zero_by_pretest(a);
        if ( 0 != b ){
          Console.WriteLine( "FAIL: _roll_to_zero_by_pretest = " + b.ToString() );
        }
      }
      _swp.Stop();
      _pretest_count       .Text = p.ToString();
      _pretest_total_time  .Text = _swp.Elapsed.Ticks.ToString();
      _pretest_average_time.Text = ( _swp.Elapsed.Ticks / p ).ToString();
      _pretest_count       .Update();
      _pretest_total_time  .Update();
      _pretest_average_time.Update();

      //
      // Exception method
      //
      var _swx = new Stopwatch();
      _swx.Start();
      for (x = 0 ; x < cycles ; x++) {
        var c = _roll_to_zero_by_exception(a);
        if ( 0 != c ){
          Console.WriteLine( "FAIL: _roll_to_zero_by_exception = " + c.ToString() );
        }
      }
      _swx.Stop();

      _excep_count       .Text = x.ToString();
      _excep_total_time  .Text = _swx.Elapsed.Ticks.ToString();
      _excep_average_time.Text = ( _swx.Elapsed.Ticks / x ).ToString();
    }

    /// 
    /// Use an if test to detect potential arithmetic overflow.
    /// When an overflow would occur, return 0.
    /// 
    ///

    /// Zero or the input integer plus 1.
    /// Underflow can not occur, becase we are always adding 1.
    /// So, we don't check for int.MinValue.
    /// 
    public int _roll_to_zero_by_pretest(int i){
      int _result = 0;
      if ( int.MaxValue == i ){
        _result = 0;
      } else {
        _result = i + 1;
      }

      return _result;
    }

    /// 
    /// Use exception processing to detect arithmetic overflow.
    /// When an underflow or overflow occurs, return 0.
    /// 
    ///

    /// Zero or the input integer plus 1.
    /// Requires "Check for overflow" compiler option setting
    public int _roll_to_zero_by_exception(int i){
      int _result = 0;
      try {
        _result = i + 1;
      } catch(OverflowException e){
        // This is the exception we are measuring.
        // For this measurement, we don't want to throw it.
        // Just let the caller measure it.
      } catch(Exception e){
        Console.WriteLine("FAIL: Unexpected Exception " + e.Message);
        throw e;
      }
      return _result;
    }
  }
}

Notes:

1. The "checked" keyword might be another way to do this.
2. The reasons for the cost of Exceptions is outside the scope of this article.

Updates:

2010.05.19 Got some comments that merited updates:
1. Handle Exception and OverflowException separately
2. Replace increment operator with addition
3. Clean up some unused code

Comments (0) Trackbacks (0)

No comments yet.


Leave a comment

You must be logged in to post a comment.

No trackbacks yet.