Pergunta

Referring to the following SE answer.

When writing

A = A ?? B;

it is the same as

if( null != A )
    A = A;
else
    A = B;

Does that mean that

if( null == A ) A = B;

would be preferred, performance wise?

Or can I assume that the compiler optimizes the code when the same object is in the ?? notation?

Foi útil?

Solução

Although performance for ?? is negligible, the side effect sometimes may not be negligible. Consider the following program.

using System;
using System.Diagnostics;
using System.Threading;

namespace TestProject
{
    class Program
    {
        private string str = "xxxxxxxxxxxxxxx";
        public string Str
        {
            get
            {
                return str;
            }
            set
            {
                if (str != value)
                {
                    str = value;
                }
                // Do some work which take 1 second
                Thread.Sleep(1000);
            }
        }

        static void Main(string[] args)
        {
            var p = new Program();

            var iterations = 10;

            var sw = new Stopwatch();
            for (int i = 0; i < iterations; i++)
            {
                if (i == 1) sw.Start();
                if (p.Str == null)
                {
                    p.Str = "yyyy";
                }
            }
            sw.Stop();
            var first = sw.Elapsed;

            sw.Reset();
            for (int i = 0; i < iterations; i++)
            {
                if (i == 1) sw.Start();
                p.Str = p.Str ?? "yyyy";
            }
            sw.Stop();
            var second = sw.Elapsed;

            Console.WriteLine(first);
            Console.WriteLine(second);

            Console.Write("Ratio: ");
            Console.WriteLine(second.TotalMilliseconds / first.TotalMilliseconds);
            Console.ReadLine();
        }

    }
}

Run result on my PC.

00:00:00.0000015
00:00:08.9995480
Ratio: 5999698.66666667

Because there is an extra assignment using ??, and the performance of the assignment sometimes might not guaranteed. This might lead to a performance issue.

I would rather use if( null == A ) A = B; instead of A = A ?? B;.

Outras dicas

Don't worry about the performance, it will be negligible.

If you are curious about it, write some code to test the performance using Stopwatch and see. I suspect you'll need to do a few million iterations to start seeing differences though.

You can also never assume about the implementation of things, they are liable to change in future - invalidating your assumptions.

My assumption is the performance difference is likely very, very small. I'd go for the null coalescing operator for readability personally, it is nice and condense and conveys the point well enough. I sometimes do it for lazy-load checking:

_lazyItem = _lazyItem ?? new LazyItem();

My advice would be to inspect the IL (intermediate language) and compare the different results. You can then see exactly what each boils down to and decide what is more optimized. But as Adam said in his comment, you're most likely best to focus on readability/maintainability over performance in something so small.

EDIT: you can view the IL by using the ILDASM.exe that ships with visual studio and open your compiled assembly.

I just tried this in C# - very quickly, so there could be an error in my method. I used the following code and determined that the second method took about 1.75 times longer than the first.
@Lockszmith: After the edit below, the ratio was 1.115 in favor of the 1st implementation

Even if they took the same time, I would personally use the language construct that is built in, as it expresses your intentions more clearly to any future compiler that may have more built-in optimizations.

@Lockszmith: I've edited the code to reflect the recommendations from the comments

var A = new object();
var B = new object();

var iterations = 1000000000;

var sw = new Stopwatch();
for (int i = 0; i < iterations; i++)
{   
    if( i == 1 ) sw.Start();
    if (A == null)
    {
        A = B;
    }
}
sw.Stop();
var first = sw.Elapsed;

sw.Reset();
for (int i = 0; i < iterations; i++)
{
    if( i == 1 ) sw.Start();
    A = A ?? B;
}
sw.Stop();
var second = sw.Elapsed;

first.Dump();
second.Dump();

(first.TotalMilliseconds / second.TotalMilliseconds).Dump("Ratio");

Yes, there is a difference.

Using Visual Studio 2017 15.9.8 targeting .NET Framework 4.6.1. Consider the sample below.

static void Main(string[] args)
{
    // Make sure our null value is not optimized away by the compiler!
    var s = args.Length > 100 ? args[100] : null;
    var foo = string.Empty;
    var bar = string.Empty;

    foo = s ?? "foo";
    bar = s != null ? s : "baz";

    // Do not optimize away our stuff above!
    Console.WriteLine($"{foo} {bar}");
}

Using ILDasm it becomes clear that the compiler does not treat those statements equally.

?? operator:

IL_001c:  dup
IL_001d:  brtrue.s   IL_0025
IL_001f:  pop
IL_0020:  ldstr      "foo"
IL_0025:  ldloc.0

Conditional null check:

IL_0026:  brtrue.s   IL_002f
IL_0028:  ldstr      "baz"
IL_002d:  br.s       IL_0030
IL_002f:  ldloc.0

Apparently, the ?? operator implies a duplication of the stack value (should be the s variable right?). I ran a simple test (multiple times) to get a feeling of which of the two is faster. Operating on string, running on this particular machine, I got these average numbers:

?? operator took:           583 ms
null-check condition took: 1045 ms

Benchmark sample code:

static void Main(string[] args)
{
    const int loopCount = 1000000000;
    var s = args.Length > 1 ? args[1] : null; // Compiler knows 's' can be null
    int sum = 0;

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

    for (int i = 0; i < loopCount; i++)
    {
        sum += (s ?? "o").Length;
    }

    watch.Stop();

    Console.WriteLine($"?? operator took {watch.ElapsedMilliseconds} ms");

    sum = 0;

    watch.Restart();

    for (int i = 0; i < loopCount; i++)
    {
        sum += (s != null ? s : "o").Length;
    }

    watch.Stop();

    Console.WriteLine($"null-check condition took {watch.ElapsedMilliseconds} ms");
}

So the answer is yes, there is a difference.

PS. StackOverflow should auto-warn posts mentioning "performance" and "negligible" in the same sentence. Only the original poster can know for sure if a time unit is neglible.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top