C # 'is' производительность оператора - PullRequest
87 голосов
/ 26 марта 2009

У меня есть программа, которая требует высокой производительности. В одном из его внутренних циклов мне нужно проверить тип объекта, чтобы увидеть, наследуется ли он от определенного интерфейса.

Один из способов сделать это - встроенная функция проверки типов в CLR. Наиболее элегантным методом, вероятно, является ключевое слово «is»:

if (obj is ISpecialType)

Другой подход заключается в предоставлении базовому классу моей собственной виртуальной функции GetType (), которая возвращает предопределенное значение перечисления (в моем случае, на самом деле, мне нужен только bool). Этот метод будет быстрым, но менее элегантным.

Я слышал, что есть инструкция IL специально для ключевого слова 'is', но это не значит, что она быстро выполняется при переводе в собственную сборку. Кто-нибудь может поделиться какой-то информацией о производительности «есть» по сравнению с другим методом?

ОБНОВЛЕНИЕ: Спасибо за все информированные ответы! Похоже, что в ответах есть несколько полезных моментов: точка зрения Эндрю о «автоматическом» выполнении броска очень важна, но данные о производительности, собранные Binary Worrier и Ian, также чрезвычайно полезны. Было бы здорово, если бы один из ответов был отредактирован и включал все этой информации.

Ответы [ 8 ]

106 голосов
/ 26 марта 2009

Использование is может снизить производительность, если после проверки типа вы приведете к этому типу. is фактически приводит объект к типу, который вы проверяете, поэтому любое последующее приведение является избыточным.

Если вы все равно собираетесь сыграть, вот лучший подход:

ISpecialType t = obj as ISpecialType;

if (t != null)
{
    // use t here
}
67 голосов
/ 26 марта 2009

Я с Ианом , вы, вероятно, не хотите этого делать.

Однако, как вы знаете, разница между ними очень мала - более 10 000 000 итераций

  • Проверка перечисления приходит в 700 миллисекунды (приблизительно)
  • Чек IS поступает в 1000 миллисекунды (приблизительно)

Лично я бы не решил эту проблему таким образом, но если бы меня заставили выбрать один метод, это была бы встроенная проверка IS, разница в производительности не стоит учитывать накладные расходы на кодирование.

Мои базовые и производные классы

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: По запросу запрашивается дополнительная информация о тестах.

Я запустил оба теста из консольного приложения (отладочная сборка), каждый тест выглядит следующим образом

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

При запуске в выпуске я получаю разницу в 60 - 70 мс, как у Иана.

Дальнейшее обновление - 25 октября 2012
Через пару лет я кое-что заметил по этому поводу: компилятор может пропустить bool b = a is MyClassB в релизе, потому что b нигде не используется.

Этот код. , .

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

. , , последовательно показывает, что проверка is проходит примерно через 57 миллисекунд, а сравнение перечислений - через 29 миллисекунд.

NB Я все же предпочел бы чек is, разница слишком мала, чтобы заботиться о

22 голосов
/ 30 ноября 2009

Хорошо, поэтому я с кем-то об этом поболтал и решил проверить это подробнее. Насколько я могу судить, производительность as и is очень хорошая по сравнению с тестированием вашего собственного члена или функции для хранения информации о типе.

Я использовал Stopwatch, который я только что узнал, возможно, не самый надежный подход, поэтому я также попытался UtcNow. Позже я также попробовал подход с использованием процессорного времени, который похож на UtcNow, включая непредсказуемые времена создания. Я также попытался сделать базовый класс неабстрактным, без виртуалов, но, похоже, он не оказал существенного влияния.

Я запустил это на Quad Q6600 с 16 ГБ ОЗУ. Даже при 50-миллиметровых итерациях числа по-прежнему отскакивают от +/- 50 миллисекунд, поэтому я бы не стал вдаваться в мелкие различия.

Интересно было увидеть, что x64 создан быстрее, но выполняется как / медленнее x86

x64 Режим выпуска:
Секундомер:
As: 561ms
Is: 597ms
Базовое свойство: 539мс
Базовое поле: 555 мс
Базовое поле RO: 552 мс
Виртуальный тест GetEnumType (): 556 мс
Виртуальный тест IsB (): 588 мс
Время создания: 10416мс

UtcNow:
As: 499мс
Is: 532ms
Базовое свойство: 479мс
Базовое поле: 502 мс
Базовое поле RO: 491мс
Виртуальный GetEnumType (): 502 мс
Виртуальный бул IsB (): 522 мс
Время создания: 285 мс (это число кажется ненадежным с UtcNow. Я также получаю 109 мс и 806 мс.)

x86 Режим выпуска:
Секундомер:
As: 391ms
Is: 423ms
Базовая недвижимость: 369мс
Базовое поле: 321мс
Базовое поле RO: 339 мс
Виртуальный тест GetEnumType (): 361 мс
Виртуальный тест IsB (): 365 мс
Время создания: 14106 мс

UtcNow:
As: 348мс
Is: 375ms
Базовое свойство: 329 мс
Базовое поле: 286мс
Базовое поле RO: 309мс
Виртуальный GetEnumType (): 321 мс
Виртуальный bool IsB (): 332 мс
Время создания: 544 мс (это число кажется ненадежным с UtcNow.)

Вот большая часть кода:

    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;
    }
}
16 голосов
/ 26 марта 2009

Андрей прав. Фактически, при анализе кода Visual Studio сообщает об этом как о ненужном приведении.

Одна идея (не зная, что ты делаешь, это что-то вроде удара в темноте), но мне всегда советовали избегать проверок, подобных этому, и вместо этого иметь другой класс. Поэтому вместо того, чтобы делать какие-либо проверки и выполнять различные действия в зависимости от типа, заставьте класс знать, как обрабатывать себя ...

например. Obj может быть ISpecialType или IType;

для обоих из них определен метод DoStuff (). Для IType он может просто возвращать или делать пользовательские вещи, тогда как ISpecialType может делать другие вещи.

Это затем полностью удаляет любое приведение, делает код чище и проще в обслуживании, и класс знает, как выполнять свои собственные задачи.

12 голосов
/ 17 июля 2012

Я сделал сравнение производительности по двум возможностям сравнения типов

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

Результат: использование «is» примерно в 10 раз быстрее !!!

Выход:

Время для сравнения типов: 00: 00: 00.456

Время для сравнения: 00: 00: 00.042

Мой код:

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);
        }
    }
}
8 голосов
/ 04 июня 2017

Точка, которую Эндрю Хейр сделал о потере производительности, когда вы выполняете проверку is, а затем приведение было допустимым, но в C # 7.0 мы можем сделать это, чтобы проверить соответствие шаблону ведьмы, чтобы избежать дальнейшего приведения:

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

Более того, если вам нужно проверять несколько типов, C # 7.0 конструкции соответствия шаблонов теперь позволяют вам делать switch для типов:

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

Подробнее о сопоставлении с образцом в C # вы можете прочитать в документации здесь .

2 голосов
/ 11 августа 2017

На случай, если кому-то интересно, я провел тесты в Unity engine 2017.1 с использованием скриптовой версии .NET4.6 (Experimantal) на ноутбуке с процессором i5-4200U. Результаты:

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

Полная статья: http://www.ennoble -studios.com / tuts / unity-c-performance-сравнение-is-vs-enum-vs-virtual-call.html

0 голосов
/ 03 июля 2014

Мне всегда советовали не проверять, как это, и вместо этого иметь другой класс. Поэтому вместо того, чтобы делать какие-либо проверки и выполнять различные действия в зависимости от типа, заставьте класс знать, как обрабатывать себя ...

например. Obj может быть ISpecialType или IType;

у них обоих определен метод DoStuff (). Для IType он может просто возвращать или делать пользовательские вещи, тогда как ISpecialType может делать другие вещи.

Это затем полностью удаляет любое приведение, делает код чище и проще в обслуживании, а класс знает, как выполнять свои собственные задачи.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...