Nullable Performance [Kit George, David Gutierrez]

Nullable Performance [Kit George, David Gutierrez]

  • Comments 2

There’s been a number of questions about Nullable performance: is doing a HasValue check performant? Here's some code which we think better mimics what your old code (scenario 1 and 2) would have looked like, vs. what we suggest you do now. Assuming in the first scenario that you had a boxed DateTime, you had to be doing a null check before casting, so we've included that into the test. The second scenario is the same, but the object is null (equivalent to HasValue=false for a Nullable type). The next test LOOKS like it has the same kind of structure, but importantly, the reference to the Value property actually does a HasValue check itself, and throws if there is no value. This means you're doing your own check for HasValue, and then it's doing a second check, so there's a perf degrade for the duplication. The last two scenarios show the best approach: do your HasValue check, and then use GetValueOrDefault. This has no HasValue check inside it, so it's performance is far better. You'll see only a slight degrade for that scenario, so things look good.

Notice in the third to last scenario, that we don't EVER do a HasValue check: and notice the great performance of this scenario. This of course only makes sense if you want to return either the value stored in the nullable, or some default (0) value otherwise. But if that IS your scenario, this should be preferred.

You’ll observe this relationship of numbers:

Result: 21285235           DateTime cast
Result:   5044263           DateTime cast, but object is null
Result: 23717132           HasValue and Value
Result:   6197101           HasValue and Value on null DateTime?
Result: 18287806           Get Value, no check
Result: 17415599           GetValueOrDefault()
Result: 20425979           HasValue and GetValueOrDefault

Note that the last option, HasValue and Get ValueOrDefault is if anything, a little faster than the cast. So overall, Nullable is as performant as it’s counterpart, and even better!

using System;

using System.Diagnostics;

 

public class Test

{

    public static void Main()

    {

        // Run the test 3 times, to ensure no first run perf issues

        PerfTestNullable();

        PerfTestNullable();

        PerfTestNullable();

    }

 

    private static void PerfTestNullable()

    {

        DateTime dateTime1 = DateTime.Now;

        Nullable<DateTime> dateTime2 = dateTime1;

        Nullable<DateTime> dateTime3 = null;

        object obj = dateTime1;

        DateTime result;

        Stopwatch sw = new Stopwatch();

 

        // Test normal DateTime cast

        sw.Start();

        for (int i = 0; i < 1000000; i++)

        {

            if (obj != null)

            {

                result = (DateTime)obj;

            }

        }

        sw.Stop();

        Console.WriteLine("Result: {0}", sw.ElapsedTicks + "\t DateTime cast");

 

        // Test normal DateTime cast.

        sw.Reset();

        sw.Start();

        obj = null;

        for (int i = 0; i < 1000000; i++)

        {

            if (obj != null)

            {

                result = (DateTime)obj;

            }

        }

        sw.Stop();

        Console.WriteLine("Result: {0}", sw.ElapsedTicks + "\t DateTime cast, but object is null");

 

        // Test generics cast, HasValue and assignment

        sw.Reset();

        sw.Start();

        for (int i = 0; i < 1000000; i++)

        {

            if (dateTime2.HasValue)

            {

                result = dateTime2.Value;

            }

        }

        sw.Stop();

        Console.WriteLine("Result: {0}", sw.ElapsedTicks + "\t HasValue and Value");

 

        // Test generics cast and no value (no assignment)

        sw.Reset();

        sw.Start();

        for (int i = 0; i < 1000000; i++)

        {

            if (dateTime3.HasValue)

            {

                result = dateTime3.Value;

            }

        }

        sw.Stop();

        Console.WriteLine("Result: {0}", sw.ElapsedTicks + "\t HasValue and Value on null DateTime?");

 

        // Test getting value with no check

        sw.Reset();

        sw.Start();

        for (int i = 0; i < 1000000; i++)

        {

            result = dateTime2.Value;

        }

        sw.Stop();

        Console.WriteLine("Result: {0}", sw.ElapsedTicks + "\t Get Value, no check");

 

        // Test GetValueOrDefault

        sw.Reset();

        sw.Start();

        for (int i = 0; i < 1000000; i++)

        {

            result = dateTime2.GetValueOrDefault();

        }

        sw.Stop();

        Console.WriteLine("Result: {0}", sw.ElapsedTicks + "\t GetValueOrDefault()");

 

        // Test GetValueOrDefault with HasValue check

        sw.Reset();

        sw.Start();

        for (int i = 0; i < 1000000; i++)

        {

            if (dateTime2.HasValue)

            {

                result = dateTime2.GetValueOrDefault();

            }

        }

        sw.Stop();

        Console.WriteLine("Result: {0}", sw.ElapsedTicks + "\t HasValue and GetValueOrDefault");

 

        Console.WriteLine();

    }

}

 

  • I ran nearly this same test the other day and came to the same conclusions you did, but from a different angle. Shame on me, though, for not knowing about this spiffy Stopwatch class. That's too cool!
  • The BCL Team explains why Nullable
Page 1 of 1 (2 items)