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