web-dev-qa-db-fra.com

Performances de l'opérateur C # 'is'

J'ai un programme qui nécessite des performances rapides. Dans l'une de ses boucles internes, j'ai besoin de tester le type d'un objet pour voir s'il hérite d'une certaine interface.

Une façon de le faire serait d'utiliser la fonctionnalité de vérification de type intégrée du CLR. La méthode la plus élégante étant probablement le mot-clé "is":

if (obj is ISpecialType)

Une autre approche consisterait à donner à la classe de base ma propre fonction GetType () virtuelle qui renvoie une valeur d'énumération prédéfinie (dans mon cas, en fait, je n'ai besoin que d'un bool). Cette méthode serait rapide, mais moins élégante.

J'ai entendu dire qu'il existe une instruction IL spécifiquement pour le mot clé 'is', mais cela ne signifie pas qu'elle s'exécute rapidement lorsqu'elle est traduite en assembly natif. Quelqu'un peut-il partager un aperçu des performances de "est" par rapport à l'autre méthode?

MISE À JOUR: Merci pour toutes les réponses éclairées! Il semble que quelques points utiles soient répartis entre les réponses: le point d'Andrew sur `` est '' d'effectuer automatiquement un casting est essentiel, mais les données de performance recueillies par Binary Worrier et Ian sont également extrêmement utiles. Ce serait formidable si l'une des réponses était modifiée pour inclure toutes toutes ces informations.

90
JubJub

L'utilisation de is peut nuire aux performances si, une fois que vous avez vérifié le type, vous effectuez un cast vers ce type. is convertit en fait l'objet dans le type que vous vérifiez, de sorte que toute conversion ultérieure est redondante.

Si vous allez quand même lancer un casting, voici une meilleure approche:

ISpecialType t = obj as ISpecialType;

if (t != null)
{
    // use t here
}
108
Andrew Hare

Je suis avec Ian , vous ne voulez probablement pas faire ça.

Cependant, juste pour que vous le sachiez, il y a très peu de différence entre les deux, plus de 10 000 000 d'itérations

  • La vérification de l'énumération arrive à 700 millisecondes (environ)
  • La vérification IS arrive à 1000 millisecondes (environ)

Personnellement, je ne réglerais pas ce problème de cette façon, mais si j'étais obligé de choisir une méthode, ce serait la vérification intégrée IS, la différence de performances ne vaut pas la peine de prendre en compte la surcharge de codage.

Mes classes de base et dérivées

class MyBaseClass
{
    public enum ClassTypeEnum { A, B }
    public ClassTypeEnum ClassType { get; protected set; }
}

class MyClassA : MyBaseClass
{
    public MyClassA()
    {
        ClassType = MyBaseClass.ClassTypeEnum.A;
    }
}
class MyClassB : MyBaseClass
{
    public MyClassB()
    {
        ClassType = MyBaseClass.ClassTypeEnum.B;
    }
}

JubJub: Comme demandé plus d'informations sur les tests.

J'ai exécuté les deux tests à partir d'une application console (une version de débogage), chaque test ressemble à ce qui suit

static void IsTest()
{
    DateTime start = DateTime.Now;
    for (int i = 0; i < 10000000; i++)
    {
        MyBaseClass a;
        if (i % 2 == 0)
            a = new MyClassA();
        else
            a = new MyClassB();
        bool b = a is MyClassB;
    }
    DateTime end = DateTime.Now;
    Console.WriteLine("Is test {0} miliseconds", (end - start).TotalMilliseconds);
}

En cours de sortie, j'obtiens une différence de 60 à 70 ms, comme Ian.

Nouvelle mise à jour - 25 octobre 2012
Après quelques années, j'ai remarqué quelque chose à ce sujet, le compilateur peut choisir d'omettre bool b = a is MyClassB en version car b n'est utilisé nulle part.

Ce code. . .

public static void IsTest()
{
    long total = 0;
    var a = new MyClassA();
    var b = new MyClassB();
    var sw = new Stopwatch();
    sw.Start();
    for (int i = 0; i < 10000000; i++)
    {
        MyBaseClass baseRef;
        if (i % 2 == 0)
            baseRef = a;//new MyClassA();
        else
            baseRef = b;// new MyClassB();
        //bool bo = baseRef is MyClassB;
        bool bo = baseRef.ClassType == MyBaseClass.ClassTypeEnum.B;
        if (bo) total += 1;
    }
    sw.Stop();
    Console.WriteLine("Is test {0} miliseconds {1}", sw.ElapsedMilliseconds, total);
}

. . . affiche systématiquement la vérification de is à environ 57 millisecondes et la comparaison d'énumérations à 29 millisecondes.

[~ # ~] nb [~ # ~] Je préférerais toujours la vérification is, la différence est trop petit pour s'en soucier

69
Binary Worrier

Ok, donc j'en discutais avec quelqu'un et j'ai décidé de tester plus. Pour autant que je sache, les performances de as et is sont toutes deux très bonnes, par rapport au test de votre propre membre ou fonction pour stocker les informations de type.

J'ai utilisé Stopwatch, ce que je viens d'apprendre n'est peut-être pas l'approche la plus fiable, j'ai donc également essayé UtcNow. Plus tard, j'ai également essayé l'approche temporelle du processeur qui ressemble à UtcNow, y compris les temps de création imprévisibles. J'ai également essayé de rendre la classe de base non abstraite sans virtuel, mais cela ne semblait pas avoir d'effet significatif.

Je l'ai exécuté sur un Quad Q6600 avec 16 Go de RAM. Même avec des itérations de 50 mil, les chiffres rebondissent toujours autour de +/- 50 millisecondes environ, donc je ne lirais pas trop les petites différences.

Il était intéressant de voir que x64 créé plus rapidement mais exécuté comme/est plus lent que x86

Mode de libération x64:
Chronomètre:
Comme: 561ms
Est: 597ms
Propriété de base: 539 ms
Champ de base: 555 ms
Base RO: 552ms
Test GetEnumType () virtuel: 556 ms
Test Virtual IsB (): 588 ms
Temps de création: 10416 ms

UtcNow:
Comme: 499ms
Est: 532ms
Propriété de base: 479 ms
Champ de base: 502 ms
Base RO: 491ms
GetEnumType virtuel (): 502 ms
BoB virtuel IsB (): 522 ms
Temps de création: 285 ms (ce nombre ne semble pas fiable avec UtcNow. J'obtiens également 109 ms et 806 ms.)

Mode de libération x86:
Chronomètre:
Comme: 391ms
Est: 423ms
Propriété de base: 369 ms
Champ de base: 321 ms
Base RO: 339ms
Test GetEnumType () virtuel: 361 ms
Test Virtual IsB (): 365 ms
Temps de création: 14106 ms

UtcNow:
Comme: 348ms
Est: 375ms
Propriété de base: 329 ms
Champ de base: 286 ms
Base RO: 309ms
GetEnumType virtuel (): 321 ms
BoB virtuel IsB (): 332 ms
Temps de création: 544 ms (ce nombre ne semble pas fiable avec UtcNow.)

Voici l'essentiel du code:

    static readonly int iterations = 50000000;
    void IsTest()
    {
        Process.GetCurrentProcess().ProcessorAffinity = (IntPtr)1;
        MyBaseClass[] bases = new MyBaseClass[iterations];
        bool[] results1 = new bool[iterations];

        Stopwatch createTime = new Stopwatch();
        createTime.Start();
        DateTime createStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            if (i % 2 == 0) bases[i] = new MyClassA();
            else bases[i] = new MyClassB();
        }
        DateTime createStop = DateTime.UtcNow;
        createTime.Stop();


        Stopwatch isTimer = new Stopwatch();
        isTimer.Start();
        DateTime isStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] =  bases[i] is MyClassB;
        }
        DateTime isStop = DateTime.UtcNow; 
        isTimer.Stop();
        CheckResults(ref  results1);

        Stopwatch asTimer = new Stopwatch();
        asTimer.Start();
        DateTime asStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] = bases[i] as MyClassB != null;
        }
        DateTime asStop = DateTime.UtcNow; 
        asTimer.Stop();
        CheckResults(ref  results1);

        Stopwatch baseMemberTime = new Stopwatch();
        baseMemberTime.Start();
        DateTime baseStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] = bases[i].ClassType == MyBaseClass.ClassTypeEnum.B;
        }
        DateTime baseStop = DateTime.UtcNow;
        baseMemberTime.Stop();
        CheckResults(ref  results1);

        Stopwatch baseFieldTime = new Stopwatch();
        baseFieldTime.Start();
        DateTime baseFieldStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] = bases[i].ClassTypeField == MyBaseClass.ClassTypeEnum.B;
        }
        DateTime baseFieldStop = DateTime.UtcNow;
        baseFieldTime.Stop();
        CheckResults(ref  results1);


        Stopwatch baseROFieldTime = new Stopwatch();
        baseROFieldTime.Start();
        DateTime baseROFieldStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] = bases[i].ClassTypeField == MyBaseClass.ClassTypeEnum.B;
        }
        DateTime baseROFieldStop = DateTime.UtcNow;
        baseROFieldTime.Stop();
        CheckResults(ref  results1);

        Stopwatch virtMethTime = new Stopwatch();
        virtMethTime.Start();
        DateTime virtStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] = bases[i].GetClassType() == MyBaseClass.ClassTypeEnum.B;
        }
        DateTime virtStop = DateTime.UtcNow;
        virtMethTime.Stop();
        CheckResults(ref  results1);

        Stopwatch virtMethBoolTime = new Stopwatch();
        virtMethBoolTime.Start();
        DateTime virtBoolStart = DateTime.UtcNow;
        for (int i = 0; i < iterations; i++)
        {
            results1[i] = bases[i].IsB();
        }
        DateTime virtBoolStop = DateTime.UtcNow;
        virtMethBoolTime.Stop();
        CheckResults(ref  results1);


        asdf.Text +=
        "Stopwatch: " + Environment.NewLine 
          +   "As:  " + asTimer.ElapsedMilliseconds + "ms" + Environment.NewLine
           +"Is:  " + isTimer.ElapsedMilliseconds + "ms" + Environment.NewLine
           + "Base property:  " + baseMemberTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Base field:  " + baseFieldTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Base RO field:  " + baseROFieldTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Virtual GetEnumType() test:  " + virtMethTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Virtual IsB() test:  " + virtMethBoolTime.ElapsedMilliseconds + "ms" + Environment.NewLine + "Create Time :  " + createTime.ElapsedMilliseconds + "ms" + Environment.NewLine + Environment.NewLine+"UtcNow: " + Environment.NewLine + "As:  " + (asStop - asStart).Milliseconds + "ms" + Environment.NewLine + "Is:  " + (isStop - isStart).Milliseconds + "ms" + Environment.NewLine + "Base property:  " + (baseStop - baseStart).Milliseconds + "ms" + Environment.NewLine + "Base field:  " + (baseFieldStop - baseFieldStart).Milliseconds + "ms" + Environment.NewLine + "Base RO field:  " + (baseROFieldStop - baseROFieldStart).Milliseconds + "ms" + Environment.NewLine + "Virtual GetEnumType():  " + (virtStop - virtStart).Milliseconds + "ms" + Environment.NewLine + "Virtual bool IsB():  " + (virtBoolStop - virtBoolStart).Milliseconds + "ms" + Environment.NewLine + "Create Time :  " + (createStop-createStart).Milliseconds + "ms" + Environment.NewLine;
    }
}

abstract class MyBaseClass
{
    public enum ClassTypeEnum { A, B }
    public ClassTypeEnum ClassType { get; protected set; }
    public ClassTypeEnum ClassTypeField;
    public readonly ClassTypeEnum ClassTypeReadonlyField;
    public abstract ClassTypeEnum GetClassType();
    public abstract bool IsB();
    protected MyBaseClass(ClassTypeEnum kind)
    {
        ClassTypeReadonlyField = kind;
    }
}

class MyClassA : MyBaseClass
{
    public override bool IsB() { return false; }
    public override ClassTypeEnum GetClassType() { return ClassTypeEnum.A; }
    public MyClassA() : base(MyBaseClass.ClassTypeEnum.A)
    {
        ClassType = MyBaseClass.ClassTypeEnum.A;
        ClassTypeField = MyBaseClass.ClassTypeEnum.A;            
    }
}
class MyClassB : MyBaseClass
{
    public override bool IsB() { return true; }
    public override ClassTypeEnum GetClassType() { return ClassTypeEnum.B; }
    public MyClassB() : base(MyBaseClass.ClassTypeEnum.B)
    {
        ClassType = MyBaseClass.ClassTypeEnum.B;
        ClassTypeField = MyBaseClass.ClassTypeEnum.B;
    }
}
22
Jared Thirsk

Andrew a raison. En fait, avec l'analyse de code, cela est signalé par Visual Studio comme une conversion inutile.

Une idée (sans savoir ce que vous faites est un peu un coup dans l'obscurité), mais on m'a toujours conseillé d'éviter de vérifier comme ça, et d'avoir à la place une autre classe. Alors plutôt que de faire quelques vérifications et d'avoir des actions différentes selon le type, faites savoir à la classe comment se traiter ...

par exemple. Obj peut être ISpecialType ou IType;

les deux ont une méthode DoStuff () définie. Pour IType, il peut simplement retourner ou faire des choses personnalisées, tandis que ISpecialType peut faire d'autres choses.

Cela supprime ensuite complètement toute conversion, rend le code plus propre et plus facile à maintenir, et la classe sait comment faire ses propres tâches.

16
Ian

J'ai fait une comparaison des performances sur deux possibilités de comparaison de types

  1. myobject.GetType ( typeof (MyClass)) ==
  2. mon objet est MyClass

Le résultat est: Utiliser "est" est environ 10 fois plus rapide !!!

Sortie:

Heure de comparaison de type: 00: 00: 00.456

Temps de comparaison Is: 00: 00: 00.042

Mon code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace ConsoleApplication3
{
    class MyClass
    {
        double foo = 1.23;
    }

    class Program
    {
        static void Main(string[] args)
        {
            MyClass myobj = new MyClass();
            int n = 10000000;

            Stopwatch sw = Stopwatch.StartNew();

            for (int i = 0; i < n; i++)
            {
                bool b = myobj.GetType() == typeof(MyClass);
            }

            sw.Stop();
            Console.WriteLine("Time for Type-Comparison: " + GetElapsedString(sw));

            sw = Stopwatch.StartNew();

            for (int i = 0; i < n; i++)
            {
                bool b = myobj is MyClass;
            }

            sw.Stop();
            Console.WriteLine("Time for Is-Comparison: " + GetElapsedString(sw));
        }

        public static string GetElapsedString(Stopwatch sw)
        {
            TimeSpan ts = sw.Elapsed;
            return String.Format("{0:00}:{1:00}:{2:00}.{3:000}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds);
        }
    }
}
12
Knasterbax

Point Andrew Hare a fait état de la perte de performances lorsque vous effectuez une vérification de is et que la conversion était valide, mais en C # 7.0, nous pouvons vérifier la correspondance du modèle de sorcière pour éviter une conversion supplémentaire plus tard:

if (obj is ISpecialType st)
{
   //st is in scope here and can be used
}

De plus, si vous avez besoin de vérifier entre plusieurs types, les constructions de correspondance de modèle C # 7.0 vous permettent désormais de faire switch sur les types:

public static double ComputeAreaModernSwitch(object shape)
{
    switch (shape)
    {
        case Square s:
            return s.Side * s.Side;
        case Circle c:
            return c.Radius * c.Radius * Math.PI;
        case Rectangle r:
            return r.Height * r.Length;
        default:
            throw new ArgumentException(
                message: "shape is not a recognized shape",
                paramName: nameof(shape));
    }
}

Vous pouvez en savoir plus sur la correspondance de modèles en C # dans la documentation ici .

9

Au cas où quelqu'un se demanderait, j'ai fait des tests dans le moteur Unity 2017.1, avec la version d'exécution de script .NET4.6 (Experimantal) sur un ordinateur portable avec un processeur i5-4200U. Résultats:

Average Relative To Local Call LocalCall 117.33 1.00 is 241.67 2.06 Enum 139.33 1.19 VCall 294.33 2.51 GetType 276.00 2.35

Article complet: http://www.ennoble-studios.com/tuts/unity-c-performance-comparison-is-vs-enum-vs-virtual-call.html

3
Gru