Click here to Skip to main content
14,029,616 members
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

14.4K views
5 bookmarked
Posted 27 Sep 2015
Licenced Ms-PL

Which Works Faster- Null Coalescing Operator or GetValueOrDefault or Conditional Operator

, 27 Sep 2015
Rate this:
Please Sign up or sign in to vote.
Benchmark, which works faster for getting the default value for a nullable type, Null Coalescing Operator or GetValueOrDefault or Conditional Operator.

Introduction

After my article Top 15 Underutilized Features of .NET provoked an interesting discussion. I was curious to learn which method is faster?? (null coalescing operator)GetValueOrDefault method or ?: (conditional operator). Recently I read in Stack Overflow that most people believe that the GetValueOrDefault method is the fastest among these three. However, I decided to do my research. I am not trying to micro-optimize. I think it won’t matter in 99% of the cases which one of the three approaches you are going to use. Usually, you should choose the one that it is easier to maintain. I am not going to argue which one is more readable because that is another topic. Rather I am going to present to you my research’s benchmark results.

Null Coalescing Operator ??

The ?? operator returns the left-hand operand if it is not null, or else it returns the right operand. A nullable type can contain a value, or it can be undefined. The ?? operator defines the default value to be returned when a nullable type is assigned to a non-nullable type.

int? x = null;
int y = x ?? -1;
Console.WriteLine("y now equals -1 because x was null => {0}", y);
int i = DefaultValueOperatorTest.GetNullableInt() ?? default(int);
Console.WriteLine("i equals now 0 because GetNullableInt() returned null => {0}", i);
string s = DefaultValueOperatorTest.GetStringValue();
Console.WriteLine("Returns 'Unspecified' because s is null => {0}", s ?? "Unspecified");

Official Documentationhttps://msdn.microsoft.com/en-us/library/ms173224.aspx

GetValueOrDefault Method

Retrieves the value of the current Nullable<T> object, or the object’s default value. It is faster than ?? operator.

float? yourSingle = -1.0f;
Console.WriteLine(yourSingle.GetValueOrDefault());
yourSingle = null;
Console.WriteLine(yourSingle.GetValueOrDefault());
// assign different default value
Console.WriteLine(yourSingle.GetValueOrDefault(-2.4f));
// returns the same result as the above statement
Console.WriteLine(yourSingle ?? -2.4f);

If you don’t specify a default value as a parameter to the method, the default value of the used type is going to be used.

Official documentation: https://msdn.microsoft.com/en-us/library/72cec0e0(v=vs.110).aspx

Conditional Operator ?:

The conditional operator (?:) returns one of two values depending on the value of a Boolean expression. Following is the syntax for the conditional operator.

condition ? first_expression : second_expression;

The condition must evaluate to true or false. If the condition is true, first_expression is evaluated and becomes the result. If the condition is false, second_expression is evaluated and becomes the result. Only one of the two expressions is evaluated.

int input = Convert.ToInt32(Console.ReadLine());
// ?: conditional operator.
string classify = (input > 0) ? "positive" : "negative";

Official documentation: https://msdn.microsoft.com/en-us/library/ty67wk28.aspx

GetValueOrDefault and Null Coalescing Operator Internals

You can find the source code for the GetValueOrDefault method on the following URL. There are two overloads for the method, one without parameters and one that requires the default value to be returned if the variable is null.

[System.Runtime.Versioning.NonVersionable]
public T GetValueOrDefault() 
{
    return value;
}

[System.Runtime.Versioning.NonVersionable]
public T GetValueOrDefault(T defaultValue) 
{
    return hasValue ? value : defaultValue;
}

As the code tells, under the hood the GetValueOrDefault method uses the conditional operator.

All of this was not enough for me so I decompiled the following code to find out how it is translated into Common Intermediate Language (CIL). For the job, I used the free Telerik .NET decompiler- Telerik JustDecompile.

public class GetValueOrDefaultAndNullCoalescingOperatorInternals
{
    public void GetValueOrDefaultInternals()
    {
        int? a = null;
        var x = a.GetValueOrDefault(7);
    }

    public void NullCoalescingOperatorInternals()
    {
        int? a = null;
        var x = a ?? 7;
    }
}

GetValueOrDefault CIL

.method public hidebysig instance void GetValueOrDefaultInternals () cil managed 
{
    .locals init (
        [0] valuetype [mscorlib]System.Nullable`1<int32> a
    )

    IL_0000: ldloca.s a
    IL_0002: initobj valuetype [mscorlib]System.Nullable`1<int32>
    IL_0008: ldloca.s a
    IL_000a: ldc.i4.7
    IL_000b: call instance int32 valuetype [mscorlib]System.Nullable`1<int32>::GetValueOrDefault(!0)
    IL_0010: pop
    IL_0011: ret
}

Null Coalescing Operator CIL

.method public hidebysig instance void NullCoalescingOperatorInternals () cil managed 
{
    .locals init (
        [0] valuetype [mscorlib]System.Nullable`1<int32> a,
        [1] valuetype [mscorlib]System.Nullable`1<int32> CS$0$0000
    )

    IL_0000: ldloca.s a
    IL_0002: initobj valuetype [mscorlib]System.Nullable`1<int32>
    IL_0008: ldloc.0
    IL_0009: stloc.1
    IL_000a: ldloca.s CS$0$0000
    IL_000c: call instance bool valuetype [mscorlib]System.Nullable`1<int32>::get_HasValue()
    IL_0011: brtrue.s IL_0014

    IL_0013: ret

    IL_0014: ldloca.s CS$0$0000
    IL_0016: call instance int32 valuetype [mscorlib]System.Nullable`1<int32>::GetValueOrDefault()
    IL_001b: pop
    IL_001c: ret
}

As far as I can cope the CIL code I think that the x ?? y is transformed into x.HasValue ? x.GetValueOrDefault() : y.  Which automatically should mean that most probably the former is going to be much faster than the later.

Which Works Faster- Null Coalescing Operator or GetValueOrDefault or Conditional Operator

To benchmark the different test cases I created a specialized profiler class.

public static class Profiler
{
    public static TimeSpan Profile(long iterations, Action actionToProfile)
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();

        var watch = new Stopwatch();
        watch.Start();
        for (int i = 0; i < iterations; i++)
        {
            actionToProfile();
        }
        watch.Stop();
          
        return watch.Elapsed;
    }

    public static string FormatProfileResults(long iterations, TimeSpan profileResults)
    {
        StringBuilder sb = new StringBuilder();
        sb.AppendLine(string.Format("Total: {0:0.00} ms ({1:N0} ticks) (over {2:N0} iterations)",
            profileResults.TotalMilliseconds, profileResults.Ticks, iterations));
        var avgElapsedMillisecondsPerRun = profileResults.TotalMilliseconds / (double)iterations;
        var avgElapsedTicksPerRun = profileResults.Ticks / (double)iterations;
        sb.AppendLine(string.Format("AVG: {0:0.00} ms ({1:N0} ticks) (over {2:N0} iterations)",
            avgElapsedMillisecondsPerRun, avgElapsedTicksPerRun, iterations));

        return sb.ToString();
    }
}

All gauges are performed in Release configuration. The correct tool to use when writing benchmarks is Stopwatch in the System.Diagnostics namespace. (I note that this namespace is well-named; everything in here is useful for diagnosing problems). DateTime.Now is the wrong tool for the job, it was designed to solve a different problem. It is harder to use than Stopwatch and has thousands or millions of times less precision. Avoid it entirely when writing benchmarks in C#.

Failing to take GC costs into account can cause you to fail to measure the true cost of an operation, or cause you to charge that cost to the wrong code. What I usually do when benchmarking, particularly if it is comparative benchmarking, is to force the garbage collector to do a full collection of all three generations before and after every test.

To force the garbage collector to do a complete collection use the following code:

GC.Collect();
GC.WaitForPendingFinalizers();

These are the six test cases that I benchmarked.

public static class GetValueOrDefaultVsNullCoalescingOperatorTest
{
    public static void ExecuteWithGetValueOrDefault()
    {
        int? a = null;
        int? b = 3;
        int? d = null;
        int? f = null;
        int? g = null;
        int? h = null;
        int? j = null;
        int? k = 7;

        var profileResult = Profiler.Profile(100000,
            () =>
            {
                var x = a.GetValueOrDefault(7);
                var y = b.GetValueOrDefault(7);
                var z = d.GetValueOrDefault(6) + f.GetValueOrDefault(3) + g.GetValueOrDefault(1) + h.GetValueOrDefault(1) + j.GetValueOrDefault(5) + k.GetValueOrDefault(8);
            });
        string formattedProfileResult = Profiler.FormatProfileResults(100000, profileResult);
        FileWriter.WriteToDesktop("ExecuteWithGetValueOrDefaultT", formattedProfileResult);
    }

    public static void ExecuteWithNullCoalescingOperator()
    {
        int? a = null;
        int? b = 3;
        int? d = null;
        int? f = null;
        int? g = null;
        int? h = null;
        int? j = null;
        int? k = 7;

        var profileResult = Profiler.Profile(100000,
            () =>
            {
                var x = a ?? 7;
                var y = b ?? 7;
                var z = (d ?? 6) + (f ?? 3) + (g ?? 1) + (h ?? 1) + (j ?? 5) + (k ?? 8);
            });
        string formattedProfileResult = Profiler.FormatProfileResults(100000, profileResult);
        FileWriter.WriteToDesktop("ExecuteWithNullCoalescingOperatorT", formattedProfileResult);
    }

    public static void ExecuteWithConditionalOperator()
    {
        int? a = null;
        int? b = 3;
        int? d = null;
        int? f = null;
        int? g = null;
        int? h = null;
        int? j = null;
        int? k = 7;

        var profileResult = Profiler.Profile(100000,
            () =>
            {
                var x = a.HasValue ? a : 7;
                var y = b.HasValue ? b : 7;
                var z = (d.HasValue ? d : 6) + (f.HasValue ? f : 3) + (g.HasValue ? g : 1) + (h.HasValue ? h : 1) + (j.HasValue ? j : 5) + (k.HasValue ? k : 8);
            });
        string formattedProfileResult = Profiler.FormatProfileResults(100000, profileResult);
        FileWriter.WriteToDesktop("ExecuteWithConditionalOperatorT", formattedProfileResult);
    }

    public static void ExecuteWithGetValueOrDefaultZero()
    {
        int? a = null;

        var profileResult = Profiler.Profile(100000,
            () =>
            {
                var x = a.GetValueOrDefault();
            });
        string formattedProfileResult = Profiler.FormatProfileResults(100000, profileResult);
        FileWriter.WriteToDesktop("ExecuteWithGetValueOrDefaultZeroT", formattedProfileResult);
    }

    public static void ExecuteWithNullCoalescingOperatorZero()
    {
        int? a = null;

        var profileResult = Profiler.Profile(100000,
            () =>
            {
                var x = a ?? 0;
            });
        string formattedProfileResult = Profiler.FormatProfileResults(100000, profileResult);
        FileWriter.WriteToDesktop("ExecuteWithNullCoalescingOperatorZeroT", formattedProfileResult);
    }

    public static void ExecuteWithConditionalOperatorZero()
    {
        int? a = null;

        var profileResult = Profiler.Profile(100000,
            () =>
            {
                var x = a.HasValue ? a : 0;
            });
        string formattedProfileResult = Profiler.FormatProfileResults(100000, profileResult);
        FileWriter.WriteToDesktop("ExecuteWithConditionalOperatorZeroT", formattedProfileResult);
    }
}

Performed Test Cases

  1. GetValueOrDefault with default value set
  2. Null Coalescing Operator with default value set
  3. Conditional Operator with default value set
  4. GetValueOrDefault without default value
  5. Null Coalescing Operator returning default value 0
  6. Conditional Operator returning default value 0

Home-made Benchmark Results

After several test runs, you can view the results from my research.

NullCoalescing GetValueOrDefault ConditionalOperator   
Time ms Ticks Time ms Ticks Time ms Ticks  
6590.08 65900785 12834.06 128340559 2868229.54 28682295356  
6550.64 65506400 12515.2 125152037 2762583.84 27625838427  
6512.58 65125820 12703.32 127033235 2776872.61 27768726079  
6612.16 66121646 13019.02 130190178 2744443.96 27444439560  
6623.67 66236731 12716.64 127166364 2750357.54 27503575389  
6503.09 65030932 12760.48 127604785 2757157.9 27571578966  
6479.25 64792548 12499.89 124998868 2737087.32 27370873195  
6521.75 65217529 12679.25 126792490 2753856.27 27538562686  
6540.68 65406786 12814.12 128141196 2754010.95 27540109549  
6617.96 66179633 13043.48 130434765 2741872.25 27418722520  
6555.186 65551881 12758.546 127585448 2764647.218 27646472173 Average

 

ConditionalOperator Zero GetValueOrDefault Zero NullCoalescing Zero  
Time ms Ticks Time ms Ticks Time ms Ticks  
3915.94 39159371 3882.08 38820849 4527.83 45278308  
3890.69 38906893 3853.21 38532131 4493.96 44939560  
3891.92 38919243 3900.99 39009895 4588.62 45886167  
3933.29 39332895 3825.76 38257618 4627.5 46274951  
3880.38 38803838 3824.2 38241964 4624.89 46248852  
4249.6 42496035 4020.71 40207055 4732.86 47328587  
3978.69 39786865 4029.73 40297288 4590.46 45904620  
3964.34 39643393 3966.89 39668937 4769 47689954  
4004.05 40040469 3938.66 39386556 4627.1 46270958  
3862.22 38622233 3882.85 38828455 4521.96 45219593  
3957.112 39571123.5 3912.508 39125074.8 4610.418 46104155 Average

Also, I have created two comparison charts for better visualization of the results. Keep in mind that I excluded the results for third test case because I don’t know why but this case was much slower than the rest.

Find below the chart containing the average execution times for all test cases. (Zoom-in or open in new tab to see it in full size)

Find below the chart containing the average ticks for all test cases. (Zoom-in or open in new tab to see it in full size)

As you can see from my results when you want to return a default value different than the default for the current Nullable type, the best performer is the null coalescing operator (??). However, when you want to return the default value for the type GetValueOrDefault method is a little bit faster.

Pro Benchmark through Telerik JustTrace

The results from my home-made benchmark were not enough for me so I installed Telerik JustTrace (2-in-1 memory and performance profiler for .NET and native apps). The results for the same test cases were slightly different. (Zoom-in or open in new tab to see it in full size)

For returning a different default value, the GetValueOrDefault method was over 8% faster than the null coalescing operator. Also, its was again a little bit faster among the test cases where the default value for the nullable type is returned.

So Far in the C# Series

1. Implement Copy Paste C# Code
2. MSBuild TCP IP Logger C# Code
3. Windows Registry Read Write C# Code
4. Change .config File at Runtime C# Code
5. Generic Properties Validator C# Code
6. Reduced AutoMapper- Auto-Map Objects 180% Faster
7. 7 New Cool Features in C# 6.0
8. Types Of Code Coverage- Examples In C#
9. MSTest Rerun Failed Tests Through MSTest.exe Wrapper Application
10. Hints For Arranging Usings in Visual Studio Efficiently
11. 19 Must-Know Visual Studio Keyboard Shortcuts – Part 1
12. 19 Must-Know Visual Studio Keyboard Shortcuts – Part 2
13. Specify Assembly References Based On Build Configuration in Visual Studio
14. Top 15 Underutilized Features of .NET
15. Top 15 Underutilized Features of .NET Part 2
16. Neat Tricks for Effortlessly Format Currency in C#
17. Assert DateTime the Right Way MSTest NUnit C# Code
18. Which Works Faster- Null Coalescing Operator or GetValueOrDefault or Conditional Operator
19. Specification-based Test Design Techniques for Enhancing Unit Tests
20. Get Property Names Using Lambda Expressions in C#
21. Top 9 Windows Event Log Tips Using C#

 

If you enjoy my publications, feel free to SUBSCRIBE
Also, hit these share buttons. Thank you!

Source Code

Reference

 

The post Which Works Faster- Null Coalescing Operator or GetValueOrDefault or Conditional Operator appeared first on Automate The Planet.

All images are purchased from DepositPhotos.com and cannot be downloaded and used for free.
License Agreement

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)

Share

About the Author

Anton Angelov
CEO Automate The Planet
Bulgaria Bulgaria
Anton Angelov is an IT Consultant and Quality Assurance Architect at Innovative Lab. He is passionate about automation testing and designing test harness and tools, having the best industry development practices in mind. In addition, he is an active blogger and the founder of Automate The Planet. He strives to make the site one of the leading authorities in Automation Testing by presenting compelling articles, inspiring ardent discussions amongst the community. He is also one of the most-rated-answer authors of questions about Test Automation Frameworks (WebDriver) on Stack Overflow.

You may also be interested in...

Comments and Discussions

 
GeneralNice work Pin
BMicka6-Oct-15 9:39
memberBMicka6-Oct-15 9:39 
In Debug configuration, or Release configuration with debugging informations is GetValueOrDefault significantly faster.
So - if you use Null Coalescing Operator ??, you don't remember distribute pure release version. No debug informations.

I tested which work faster too, in release configuration, and my results are still ambivalent.

If I run the same release application on two different PC,

Here is ?? two times faster than GetValueOrDefault[^]

Here is GetValueOrDefault is two times faster than ??[^]

Functions are discrete, used StopWatch and GC cleaning.

Code here:

private void compare()
{

    int? a = null;
    int? b = 44;
    int? c = null;
    int? d = 33;
    int defaulta = 11, defaultb = 12, defaultc = -5, defaultd = 14;
    int iterCount = 50000000 - 1;

    var lstFnc = new List<Tuple<string, Func<string, int?, int?, int?, int?, long>>>();

    Func<string, int?, int?, int?, int?, long> functionGetValueOrDefault = (name, pa, pb, pc, pd) =>
    {
        gcCollect();

        long r = 0;
        var sw = Stopwatch.StartNew();
        for (int iter = 0; iter < iterCount; iter++)
            r += pa.GetValueOrDefault(defaulta)
                + pb.GetValueOrDefault(defaultb)
                + pc.GetValueOrDefault(defaultc)
                + pd.GetValueOrDefault(defaultd);
        sw.Stop();
        updateRow(name, sw.ElapsedMilliseconds, sw.ElapsedTicks, r);
        return sw.ElapsedMilliseconds;
    };

    Func<string, int?, int?, int?, int?, long> function2Questions = (name, pa, pb, pc, pd) =>
    {
        gcCollect();

        long r = 0;
        var sw = Stopwatch.StartNew();
        for (int iter = 0; iter < iterCount; iter++)
            r += (pa ?? defaulta)
                + (pb ?? defaultb)
                + (pc ?? defaultc)
                + (pd ?? defaultd);
        sw.Stop();
        updateRow(name, sw.ElapsedMilliseconds, sw.ElapsedTicks, r);
        return sw.ElapsedMilliseconds;
    };

    if (cbGetValueOrDefault.IsChecked.GetValueOrDefault())
        lstFnc.Add(new Tuple<string, Func<string, int?, int?, int?, int?, long>>("GetValueOrDefault", functionGetValueOrDefault));
    if (cb2Q.IsChecked.GetValueOrDefault())
        lstFnc.Add(new Tuple<string, Func<string, int?, int?, int?, int?, long>>("TwoQuestions", function2Questions));

    Random rand = new Random();
    lstFnc = lstFnc.OrderBy(f => rand.Next(10)).ToList();  // shuffle function order

    DataTable dt = new DataTable(); // result datatable
    dt.Columns.Add(new DataColumn("Cycle", typeof(int)));

    foreach (var item in lstFnc)
    {
        dt.Columns.Add(new DataColumn(item.Item1 + "_Ms", typeof(long)));
        dt.Columns.Add(new DataColumn(item.Item1 + "_Ticks", typeof(long)));
        dt.Columns.Add(new DataColumn(item.Item1 + "_Result", typeof(long)));
    }

    for (int mainCycle = 0; mainCycle < 10; mainCycle++)
    {
        this.row = dt.NewRow();
        row["Cycle"] = mainCycle;
        dt.Rows.Add(row);

        foreach (var item in lstFnc)
        {
            double ms = item.Item2(item.Item1, a, b, c, d); // function execution
        }
        grid.ItemsSource = dt.DefaultView;
        DoEvents();
    }
}

BugTypo Pin
Member 1007656928-Sep-15 9:44
memberMember 1007656928-Sep-15 9:44 
GeneralRe: Typo Pin
Anton Angelov28-Sep-15 9:56
memberAnton Angelov28-Sep-15 9:56 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web04 | 2.8.190419.4 | Last Updated 27 Sep 2015
Article Copyright 2015 by Anton Angelov
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid