4

Cost of exceptions

 2 years ago
source link: https://gunnarpeipman.com/cost-of-exceptions/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Cost of exceptions

Hopefully the era of leprosy and corona is over for this time and it’s time to get back to blogging. Exceptions are powerful feature of object-oriented languages as far as they are used like they are thought to use – throw exception only when something really unexpected happens. This advice should be taken seriously – here’s why.

Back in days when I was young and beautyful (now I’m only beautyful) I found utility application to get documents and their metadata out from SharePoint 2001. Although servers were powerful the exporting process was very slow. I took source code of utility and added bunch of sanity checks before exception handling to make sure that code doesn’t hit try-catch blocks if it can be avoided. Instead of 10 days our exports took 4.5 days after my little tweaks.

It’s hard to notice the effect of exceptions if we measure just one exception but they can be fatal to performance when they appear in loops. Let’s dig a little bit deeper.

How exceptions are handled?

Those who think exceptions are just fancy way to return errors are usually surprised when they hear how complex can things be internally – somewhere deep in Common Language Runtime (CLR).

The excellent book Expert .NET 2.0 IL Assembler by Serge Lidin describes what goes on under the hood.

The execution engine of the CLR processes an exception in two passes. The first pass determines which, if any, of the managed handlers will process the exception. Starting at the top of the Exception Handling (EH) table for the current method frame, the execution engine compares the address where the exception occured to the TryOffset and TryLength entries of each EH clause. If it finds that the exception happened in guarded block, the execution engine checks to see whether the handler specified in this clause will process the exception. … If none of the clauses in the EH table for the current method is suited to handling the exception, the execution engine steps up the call stack and starts checking the exception against EH tables of the method that called the method where the exception occured.

During the second pass, the finally and fault handlers are invoked with an empty evaluation stack. These handlers do nothing about the exception itself and work only with method arguments and local variables, so the execution engine doesn’t bother providing the exception object.

Without any numbers there’s are two alert reds for me:

  1. Processing in two phases
  2. Climbing up in method call stack and checking for exception handlers

These two activities both take probably more time than “usual” things we are doing in code.

Exception versus avoiding exception

Let’s get into code and numbers to get better understanding about exceptions effect to performance. I’m using simple program that fills list with some strings and nulls. After this let’s ask string length for each element in list and measure how long it takes.

Let’s start with version where asking string length is in try-catch block.

var list = new List<string>();
for (int i = 0; i < Math.Pow(10, 6); i++)
{
    if (i == 1 || i % 2 != 0)
    {
        list.Add(i.ToString());
    }
    else
    {
        list.Add(null);
    }
}

var watch = new Stopwatch();
watch.Start();

foreach (var s in list)
{
    try
    {
        var i = s.Length;
    }
    catch (Exception ex)
    {
        var e = ex.Message;
    }
}

watch.Stop();
Console.WriteLine(watch.Elapsed);

On my machine this code ran through with 4.58 seconds.

Let’s remove exception handling and replace it with null check so we don’t ask length of null-string.

var list = new List<string>();
for (int i = 0; i < Math.Pow(10, 6); i++)
{
    if (i == 1 || i % 2 != 0)
    {
        list.Add(i.ToString());
    }
    else
    {
        list.Add(null);
    }
}

var watch = new Stopwatch();
watch.Start();

foreach(var s in list)
{
    if(s == null)
    {
        continue;
    }

var i = s.Length;
}

watch.Stop();
Console.WriteLine(watch.Elapsed);

After this modification the code takes 0.008 seconds to run. It’s roughly taken 570 times faster than letting code fall to exception. So, cost of exceptions can be very high.

Wrapping up

Exceptions are powerful feature and without exceptions we should think about our own mechanism how to organize error handling in our code. Like all other powerful features exceptions come with cost. This blog post demonstrated one aspect of it and one popular way how exceptions are abused. The code samples here went from 4.5 seconds to 0.008 seconds by avoiding exceptions. For long processes the win in time can be way bigger.

Liked this post? Empower your friends by sharing it!

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK