С# '- это операционная производительность

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

Один из способов сделать это - использовать встроенную функцию проверки типов CLR. Самый элегантный способ, по-видимому, ключевое слово "есть":

if (obj is ISpecialType)

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

Я слышал, что есть инструкция IL специально для ключевого слова is is, но это не значит, что она выполняется быстро при переходе на собственную сборку. Может ли кто-нибудь рассказать о производительности "is" по сравнению с другим методом?

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

Ответ 1

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

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

ISpecialType t = obj as ISpecialType;

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

Ответ 2

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

Однако, как вы знаете, разница между ними составляет очень мало, более 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 мс, например, Ian.

Дальнейшее обновление - 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, разница слишком мала, чтобы заботиться о

Ответ 3

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

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

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

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

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

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

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

UtcNow:
Как: 348мс
Is: 375ms
Базовое свойство: 329ms
Базовое поле: 286 мс
Базовое поле RO: 309ms
Виртуальный GetEnumType(): 321ms
Виртуальный bool IsB(): 332ms
Время создания: 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;
    }
}

Ответ 4

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

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

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

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

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

Ответ 5

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

  • myobject.GetType() == typeof (MyClass)
  • myobject - это 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);
        }
    }
}

Ответ 6

Точка Andrew Hare сделала о производительности теряется, когда вы выполняете is проверка, а затем отливать была действителен, но в С# 7.0 мы можем сделать, это матч проверки ведьмы шаблон, чтобы избежать дополнительного бросания позже:

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

Еще больше, если вам нужно проверить между несколькими типами конструкций сопоставления шаблонов С# 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));
    }
}

Подробнее о сопоставлении шаблонов в С# вы можете прочитать здесь.

Ответ 7

В случае, если кто-то задается вопросом, я провел тесты в Unity engine 2017.1 со сценарием версии .NET.NET 6.5 (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-comparison-is-vs-enum-vs-virtual-call.html

Ответ 8

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

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

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

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