1
votes

Y a-t-il des frais généraux à utiliser massivement.? opérateurs pour les comparaisons champ / propriété?

Si nous avons 2 objets et que nous avons surchargé un opérateur d'égalité pour comparer chacun d'eux, devrions-nous nous lasser de l'utilisation du? opérateur (par exemple firstObject? .MyProperty == secondObject? .MyProperty ) si nous finissons par utiliser cela sur, disons 20-30 propriétés?

Fondamentalement, pour les grands objets avec beaucoup de propriétés, est-ce mieux pour faire la comparaison suivante

(firstObject == null && secondObject == null || !(firstObject == null ^ secondObject == null)) 
&&  (<property comparisons without ?>)

ou simplement

Merci vous.


6 commentaires

Lorsque vous l'avez chargé dans une boucle qui a effectué un milliard de vérifications d'égalité, quels ont été les résultats du test de performance?


Si vous avez beaucoup de champs, il est plus propre de vérifier d'abord l'objet entier pour null et d'éviter de répéter le code


Il n'y a pas mieux, parce que vous n'avez donné aucun domaine dans lequel quelque chose peut être meilleur. Le conditionnel nul générera plus d'IL, mais semble plus net. Pourtant, les deux font la même chose. En fin de compte, toute différence de performances pourrait bien être optimisée


@CaiusJard avec? comparaisons d'opérateurs J'ai obtenu 00: 03: 57.3792339, sans? c'est 00: 03: 48.5972988 (exécuté sur Release, sans débogage). Je ne sais pas si la différence est réelle ou non, mais cela ne semble pas avoir beaucoup d'impact.


Je suppose que cela revient à une question de "combien d'heures / jours / années faudra-t-il avant que le système de production n'ait fait un milliard de comparaisons légitimes, et les 9 secondes économisées valent-elles la peine par rapport à la propreté du code. . ". Si prod fait un milliard par rapport à une heure, cela pourrait représenter une économie raisonnable; s'il y a un an de fonctionnement avant que le compte de comparaison ne soit atteint, cela ne vaut pas la laideur (IMHO)


@TheGeneral Null-conditionnel (alias "opérateur Elvis") génère moins IL.


3 Réponses :


0
votes

Non, sous le capot, ce sont les mêmes. Le ?. est plus lisible et fait exactement la même chose dans les coulisses.

Techniquement, ils pourraient être légèrement différents à cause de l'évaluation des courts-circuits (s'ils sont tous les deux nuls, cela peut ignorer tout le reste de l'alternative) mais vous regardez les microsecondes (si c'est le cas). Vous perdriez plus du temps nécessaire pour charger du code plus long dans la RAM.

Si vous voulez vous le prouver, vous pouvez faire ce que @Caius Jard a suggéré et le mettre dans une boucle qui effectue des millions / milliards de vérifications d'égalité.


0 commentaires

3
votes

Sous le capot, ils sont différents . Le ?. est plus lisible et plus rapide (semble-t-il).

Preuve

Tout d'abord, considérons cet exemple (ciblant .NET Framework 4.7.2 ):

UsingElvisOperator:   1704 ms
UsingIfsAndButs:      2815 ms

Compilez-le en mode Release pour la plate-forme Any CPU . Décompilons maintenant le .exe produit en utilisant ildasm .

Les instructions pour l ' opérateur Elvis :

.method private hidebysig static void  UsingIfsAndButs(class OperatorTest.Program/Foo foo) cil managed
{
  // Code size       49 (0x31)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  brtrue.s   IL_0006
  IL_0003:  ldnull
  IL_0004:  br.s       IL_0024
  IL_0006:  ldarg.0
  IL_0007:  call       instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
  IL_000c:  dup
  IL_000d:  brtrue.s   IL_0013
  IL_000f:  pop
  IL_0010:  ldnull
  IL_0011:  br.s       IL_0024
  IL_0013:  call       instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
  IL_0018:  dup
  IL_0019:  brtrue.s   IL_001f
  IL_001b:  pop
  IL_001c:  ldnull
  IL_001d:  br.s       IL_0024
  IL_001f:  call       instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
  IL_0024:  brfalse.s  IL_0030
  IL_0026:  ldstr      "OK"
  IL_002b:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0030:  ret
} // end of method Program::UsingIfsAndButs

Et les instructions pour les if-checks

.method private hidebysig static void  UsingElvisOperator(class OperatorTest.Program/Foo foo) cil managed
{
  // Code size       53 (0x35)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  brfalse.s  IL_0034
  IL_0003:  ldarg.0
  IL_0004:  callvirt   instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
  IL_0009:  brfalse.s  IL_0034
  IL_000b:  ldarg.0
  IL_000c:  callvirt   instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
  IL_0011:  callvirt   instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
  IL_0016:  brfalse.s  IL_0034
  IL_0018:  ldarg.0
  IL_0019:  callvirt   instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
  IL_001e:  callvirt   instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
  IL_0023:  callvirt   instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
  IL_0028:  brfalse.s  IL_0034
  IL_002a:  ldstr      "OK"
  IL_002f:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0034:  ret
} // end of method Program::UsingElvisOperator

De toute évidence, les instructions générées sont différentes.

Comparaison des performances

Lancer les deux méthodes un milliard de fois, sur ma machine, me donne les résultats suivants:

class Program
{
    class Foo
    {
        public Foo Bar { get; set; }
    }

    static void Main(string[] args)
    {
        var foo = new Foo();

        var t0 = DateTime.UtcNow.Ticks;
        for (int i = 0; i < 1000000000; i++)
        {
            UsingElvisOperator(foo);
            // UsingIfsAndButs(foo);
        }
        var t1 = DateTime.UtcNow.Ticks;

        Console.WriteLine($"Elapsed time: {(t1 - t0) / 10000} ms");
    }

    private static void UsingIfsAndButs(Foo foo)
    {
        if (foo?.Bar?.Bar?.Bar != null)
        {
            Console.WriteLine("OK");
        }
    }

    private static void UsingElvisOperator(Foo foo)
    {
        if (foo != null && foo.Bar != null && foo.Bar.Bar != null && foo.Bar.Bar.Bar != null)
        {
            Console.WriteLine("OK");
        }
    }
}


2 commentaires

Il est important de noter cependant que vous n'avez pas évalué la question des opérations. Oops. de toute façon, plus un pour l'effort, et j'utiliserais toujours le conditionnel nul moi-même, peu importe


@TheGeneral Vous avez raison. Je ne comprends pas parfaitement le PO, alors j'ai fait une interprétation créative.



2
votes

je veux juste partager mon résumé de benchmarking qui vérifie l'objet de test nul et non nul, ce qui montre un avantage en vitesse pour les "ifs":

[SimpleJob(RunStrategy.Throughput, launchCount: 2, warmupCount: 1, targetCount: 2, id: "NewJob")]
public class ElvisBenchmarks
{
    Foo foo;
    Foo foo_null;
    Boolean isNull;
    
    public ElvisBenchmarks()
    {
         foo = new Foo();
    }

    [Benchmark]
    public void UsingIfsAndButs()
    {
        isNull = (foo != null && foo.Bar != null && foo.Bar.Bar != null && foo.Bar.Bar.Bar != null)
                ? true : false;

        isNull = (foo_null != null && foo_null.Bar != null && foo_null.Bar.Bar != null && foo_null.Bar.Bar.Bar != null)
                ? true : false;
    }
        
    [Benchmark]
    public void UsingElvisOperator()
    {
        isNull = (foo?.Bar?.Bar?.Bar != null) ? true : false;
        isNull = (foo_null?.Bar?.Bar?.Bar != null) ? true : false;
    }
}

class Foo
{
    public Foo Bar { get; set; }
}

et le code: p>

// * Summary *
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.900 (1909/November2018Update/19H2)
Intel Core i5-7300U CPU 2.60GHz (Kaby Lake), 1 CPU, 4 logical and 2 physical cores
.NET Core SDK=3.1.301

  [Host] : .NET Core 3.1.5 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.27001), X64 RyuJIT

Job=NewJob  IterationCount=2  LaunchCount=2  
RunStrategy=Throughput  WarmupCount=1  

|             Method |     Mean |     Error |    StdDev |
|
------------------- |---------:|----------:|----------:|
|    UsingIfsAndButs | 1.518 ns | 0.4293 ns | 0.0664 ns |
| UsingElvisOperator | 2.144 ns | 0.2124 ns | 0.0329 ns |


1 commentaires

Intéressant ... des théories sur pourquoi cela se produit? Si vous testez non pas les Foo imbriqués mais les propriétés de foo, je pense que le résultat sera le contraire.