web-dev-qa-db-fra.com

Points d'interrogation doubles ('??') vs if lors de l'attribution de la même variable

En se référant à ce qui suit réponse SE .

Lors de l'écriture

A = A ?? B;

c'est la même chose que

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

Est-ce que ça veut dire que

if( null == A ) A = B;

serait préférable, en termes de performances?

Ou puis-je supposer que le compilateur optimise le code lorsque le même objet est dans le ?? notation?

15
Lockszmith

Bien que les performances de ?? Soient négligeables, l'effet secondaire peut parfois ne pas être négligeable. Considérez le programme suivant.

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();
        }

    }
}

Exécutez le résultat sur mon PC.

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

Parce qu'il y a une affectation supplémentaire utilisant ??, Et les performances de l'affectation peuvent parfois ne pas être garanties. Cela peut entraîner un problème de performances.

Je préférerais utiliser if( null == A ) A = B; au lieu de A = A ?? B;.

2
VCD

Ne vous inquiétez pas de la performance, ce sera négligeable.

Si vous en êtes curieux, écrivez du code pour tester les performances en utilisant Stopwatch et voyez. Je suppose que vous aurez besoin de faire quelques millions d'itérations pour commencer à voir des différences.

Vous ne pouvez jamais non plus présumer de la mise en œuvre des choses, elles sont susceptibles de changer à l'avenir - annulant vos hypothèses.

Mon hypothèse est que la différence de performance est probablement très, très faible. Je choisirais l'opérateur de fusion nul pour la lisibilité personnellement, il est agréable et condensé et transmet assez bien le point. Je le fais parfois pour la vérification paresseux:

_lazyItem = _lazyItem ?? new LazyItem();
12
Adam Houldsworth

J'ai juste essayé ceci en C # - très rapidement, donc il pourrait y avoir une erreur dans ma méthode. J'ai utilisé le code suivant et j'ai déterminé que la deuxième méthode prenait environ 1,75 fois plus de temps que la première.
@ Lockszmith: Après la vérification ci-dessous, le ratio était de 1.115 en faveur de la 1ère implémentation

Même s'ils prenaient le même temps, j'utiliserais personnellement la construction de langage intégrée, car elle exprime plus clairement vos intentions à tout futur compilateur qui pourrait avoir plus d'optimisations intégrées.

@ Lockszmith: J'ai édité le code pour refléter les recommandations des commentaires

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");
3
Stephen Hewlett

Mon conseil serait d'inspecter l'IL (langage intermédiaire) et de comparer les différents résultats. Vous pouvez alors voir exactement en quoi chacun se résume et décider de ce qui est le plus optimisé. Mais comme Adam l'a dit dans son commentaire, il est probablement préférable de vous concentrer sur la lisibilité/maintenabilité plutôt que sur les performances dans quelque chose d'aussi petit.

EDIT: vous pouvez afficher l'IL en utilisant ILDASM.exe fourni avec Visual Studio et ouvrir votre Assembly compilé.

3
OnResolve

Oui, il y a une différence.

Utilisation du ciblage Visual Studio 2017 15.9.8.NET Framework 4.6.1. Considérez l'exemple ci-dessous.

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}");
}

En utilisant ILDasm, il devient clair que le compilateur ne traite pas ces instructions de la même manière.

?? opérateur:

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

Contrôle nul conditionnel:

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

Apparemment, l'opérateur ?? Implique une duplication de la valeur de la pile (devrait être la variable s, n'est-ce pas?). J'ai effectué un test simple (plusieurs fois) pour obtenir une impression dont des deux est la plus rapide. Opérant sur string, fonctionnant sur cette machine particulière, j'ai obtenu ces nombres moyens:

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

Exemple de code de référence:

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");
}

Donc la réponse est oui, il y a une différence.

PS. StackOverflow devrait automatiquement avertir les messages mentionnant "performance" et "négligeable" dans la même phrase. Seule l'affiche originale peut savoir avec certitude si une unité de temps est négligeable.

1
l33t