92 votes

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

J'ai un programme qui nécessite des performances rapides. Dans l'une de ses boucles internes, je dois 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é intégrée de vérification des types du CLR. La méthode la plus élégante est probablement le mot-clé "is" :

if (obj is ISpecialType)

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

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

UPDATE : Merci pour toutes ces réponses éclairées ! Il semble que quelques points utiles soient dispersés parmi les réponses : Le point d'Andrew sur le fait que "est" effectue 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 les points suivants todo de ces informations.

109voto

Andrew Hare Points 159332

Utilisation de is peut nuire aux performances si, après avoir vérifié le type, vous effectuez un casting 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 devez quand même faire un lancer, voici une meilleure approche :

ISpecialType t = obj as ISpecialType;

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

69voto

Binary Worrier Points 27424

Je suis avec Ian vous n'avez probablement pas envie de faire ça.

Cependant, juste pour que vous sachiez, il y a très peu de différence entre les deux, sur 10.000.000 itérations

  • Le contrôle de l'enum est effectué à 700 millisecondes (environ)
  • Le chèque de l'IS s'élève à 1000 millisecondes (environ)

Personnellement, je ne résoudrais pas ce problème de cette façon, mais si je devais choisir une méthode, ce serait la vérification IS intégrée, la différence de performance 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) et 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 exécutant la version, j'obtiens une différence de 60 à 70 ms, comme Ian.

Nouvelle mise à jour - 25 octobre 2012
Après quelques années d'absence, j'ai remarqué quelque chose à ce sujet, le compilateur peut choisir d'omettre bool b = a is MyClassB dans la 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);
}

. montre systématiquement que is La vérification de l'enum se fait en 57 millisecondes environ, et la comparaison de l'enum se fait en 29 millisecondes.

NB Je préfère encore le is vérifier, la différence est trop faible pour s'en préoccuper

22voto

Jared Thirsk Points 371

Ok, je discutais de ce sujet avec quelqu'un et j'ai décidé de le tester davantage. Pour autant que je puisse dire, les performances de as y is sont toutes deux très bonnes, comparées au test de votre propre membre ou fonction pour stocker les informations de type.

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

Je l'ai exécuté sur un Quad Q6600 avec 16 Go de RAM. Même avec 50 millions d'itérations, les chiffres oscillent toujours autour de +/- 50 millisecondes, je ne m'attarderais donc pas trop sur ces différences mineures.

Il était intéressant de voir que x64 créait plus rapidement mais exécutait aussi/est plus lent que x86

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

UtcNow :
Comme : 499ms
Est : 532ms
Propriété de base : 479ms
Champ de base : 502ms
Champ RO de base : 491ms
GetEnumType() virtuel : 502ms
Bool virtuel IsB() : 522ms
Temps de création : 285ms (Ce chiffre semble peu fiable avec UtcNow. J'obtiens aussi 109ms et 806ms).

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

UtcNow :
Comme : 348ms
C'est : 375 ms
Propriété de base : 329ms
Champ de base : 286ms
Champ RO de base : 309ms
Virtuel GetEnumType() : 321ms
Bool virtuel IsB() : 332ms
Temps de création : 544ms (Ce chiffre 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;
    }
}

16voto

Ian Points 13892

Andrew a raison. En fait, dans le cadre de l'analyse du code, Visual Studio signale qu'il s'agit d'un cast inutile.

Une idée (sans savoir ce que vous faites, c'est un peu un coup dans le vide), mais on m'a toujours conseillé d'éviter ce genre de contrôle et de créer une autre classe. Ainsi, plutôt que de faire des vérifications et d'avoir des actions différentes en fonction du type, faites en sorte que la classe sache comment se traiter elle-même...

Par exemple, Obj peut être ISpecialType ou IType ;

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

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

13voto

Knasterbax Points 1383

J'ai effectué une comparaison des performances de deux possibilités de comparaison de types.

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

Le résultat est le suivant : En utilisant es est environ 10x plus rapide ! !!

Sortie :

C'est l'heure de la comparaison des types : 00:00:00.456

Temps pour Is-Comparison : 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);
        }
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X