Интерфейс Кастинг против Класса Кастинг - PullRequest
17 голосов
/ 03 января 2011

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

Учитывая, что я хочу создать более правильную эвристику, когда речь заходит о программировании, мне предложили задать этот вопросдля гуру .NET: Является ли приведение интерфейса быстрее, чем приведение классов?

Чтобы привести пример кода, допустим, что он существует:

public interface IEntity { IParent DaddyMommy { get; } }
public interface IParent : IEntity { }
public class Parent : Entity, IParent { }
public class Entity : IEntity
{
    public IParent DaddyMommy { get; protected set; }
    public IParent AdamEve_Interfaces
    {
        get
        {
            IEntity e = this;
            while (e.DaddyMommy != null)
                e = e.DaddyMommy as IEntity;
            return e as IParent;
        }   
    }
    public Parent AdamEve_Classes
    {
        get
        {
            Entity e = this;
            while (e.DaddyMommy != null)
                e = e.DaddyMommy as Entity;
            return e as Parent;
        }
    }
}

Итак, AdamEve_Interfaces быстрее, чем AdamEve_Classes?Если да, то сколько?И, если вы знаете ответ, почему?

Ответы [ 6 ]

17 голосов
/ 04 января 2011

Ряд ответов здесь предлагает сравнительный анализ, который является шагом в правильном направлении, но только первым шагом в пути.

Моя команда провела большой анализ профилей и сравнительного анализа в этой области,Короткая версия - да , существуют ситуации, в которых интерфейсы требуют небольших, но измеримых затрат производительности. Однако фактическая стоимость зависит от множества факторов, включая количество поддерживаемых интерфейсов, количество интерфейсов, на которые ссылается данная ссылка, каков порядок доступа и т. Д.CLR имеет множество эвристик, разработанных для ускорения доступа к интерфейсу в обычных случаях.

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

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

Что касается ссылки на бросание и ловлю: затраты на производительностьметания и ловли должны быть неуместны.Исключения по определению исключительные , а не общие .Кроме того, исключения обычно указывают, что что-то скоро остановится;обычно не имеет значения, останавливается ли что-то настолько быстро, насколько это возможно.Если вы находитесь в ситуации, когда ваша производительность ограничена исключениями, вам нужно решить более серьезные проблемы: прекратить выдавать столько исключений .Исключение должно быть крайне редким.

7 голосов
/ 03 января 2011

Вы должны были бы измерить.

Но если приведение становится (потенциальным) узким местом в вашем коде, то вы уже далеко за спагетти в меню проблем.

7 голосов
/ 03 января 2011

Посмотрите здесь:

http://thatstoday.com/robbanp/blog/6/25/csharp-performance--cast-vs-interface

И, да, вы, кажется, правы.

Редактировать Ну, похоже, я ошибся. И, как прокомментировал мой "patrício" Мартиньо Фернандес ниже, приведенная выше ссылка является полностью поддельной (но я буду держать ее здесь, ради честного редактирования).

В настоящее время у меня есть немного свободного времени, поэтому я написал простой код для измерения производительности:

public partial class Form1 : Form
{
    private const int Cycles = 10000000;

    public interface IMyInterface
    {
        int SameProperty { get; set; }
    }

    public class InterfacedClass : IMyInterface
    {
        public int SameProperty { get; set; }
    }

    public class SimpleClass
    {
        public int SameProperty { get; set; }
    }

    public struct InterfacedStruct : IMyInterface
    {
        public int SameProperty { get; set; }
    }

    public struct SimpleStruct
    {
        public int SameProperty { get; set; }
    }

    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e) {
        var simpleClassTime = MeasureSimpleClass();
        var interfacedClassTime = MeasureInterfacedClass();
        var simpleStructTime = MeasureSimpleStruct();
        var interfacedStructTime = MeasureInterfacedStruct();

        var message = string.Format(
            "simpleClassTime = {0}\r\ninterfacedClassTime = {1}\r\nsimpleStructTime = {2}\r\ninterfacedStructTime = {3}",
            simpleClassTime,
            interfacedClassTime,
            simpleStructTime,
            interfacedStructTime
        );

        textBox.Text = message;
    }

    private static long MeasureSimpleClass() {
        var watch = Stopwatch.StartNew();
        var obj = new SimpleClass();

        for (var i = 0; i < Cycles; i++)
        {
            obj.SameProperty = i;
            var j = obj.SameProperty;
            obj.SameProperty = j;
        }

        return watch.ElapsedMilliseconds;
    }

    private static long MeasureInterfacedClass() {
        var watch = Stopwatch.StartNew();
        IMyInterface obj = new InterfacedClass();

        for (var i = 0; i < Cycles; i++) {
            obj.SameProperty = i;
            var j = obj.SameProperty;
            obj.SameProperty = j;
        }

        return watch.ElapsedMilliseconds;
    }

    private static long MeasureSimpleStruct()
    {
        var watch = Stopwatch.StartNew();
        var obj = new SimpleStruct();

        for (var i = 0; i < Cycles; i++)
        {
            obj.SameProperty = i;
            var j = obj.SameProperty;
            obj.SameProperty = j;
        }

        return watch.ElapsedMilliseconds;
    }

    private static long MeasureInterfacedStruct()
    {
        var watch = Stopwatch.StartNew();
        IMyInterface obj = new InterfacedStruct();

        for (var i = 0; i < Cycles; i++)
        {
            obj.SameProperty = i;
            var j = obj.SameProperty;
            obj.SameProperty = j;
        }

        return watch.ElapsedMilliseconds;
    }
}

И результат:

simpleClassTime = 274
interfacedClassTime = 339
simpleStructTime = 247
interfacedStructTime = 302

Я действительно привык думать, что интерфейс будет быстрее для class типов и медленнее для struct (так как в последнем случае участвует бокс / распаковка), но это не так: конкретный класс / структура ссылка всегда быстрее, кажется.

Кроме того, для кого это может касаться: я считаю, что производительность не является хорошим критерием для принятия решения о том, должен ли интерфейс использоваться или не использоваться. Разница, как другие говорили здесь, ничтожна.

5 голосов
/ 03 января 2011

Вы пробовали протестировать это?Вот цикл, который выполняется 10 000 000 раз.На моей машине версия интерфейса занимает около 440 мс, а версия класса - около 410 мс.Так что довольно близко, но в целом выигрывает версия класса.

using System;

namespace ConsoleApplication1
{
    public interface IEntity { IParent DaddyMommy { get; } }
    public interface IParent : IEntity { }
    public class Parent : Entity, IParent { }
    public class Entity : IEntity
    {
        public IParent DaddyMommy { get; protected set; }
        public IParent AdamEve_Interfaces
        {
            get
            {
                IEntity e = this;
                while (this.DaddyMommy != null)
                    e = e.DaddyMommy as IEntity;
                return e as IParent;
            }
        }
        public Parent AdamEve_Classes
        {
            get
            {
                Entity e = this;
                while (this.DaddyMommy != null)
                    e = e.DaddyMommy as Entity;
                return e as Parent;
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Entity X = new Entity();
            Parent P;
            IParent IP;
            System.Diagnostics.Stopwatch ST = new System.Diagnostics.Stopwatch();
            Int32 i;

            ST.Start();
            for (i = 0; i < 10000000; i++)
            {
                IP = X.AdamEve_Interfaces;
            }
            ST.Stop();
            System.Diagnostics.Trace.WriteLine(ST.ElapsedMilliseconds);

            ST.Reset();

            ST.Start();
            for (i = 0; i < 10000000; i++)
            {
                P = X.AdamEve_Classes;
            }
            ST.Stop();
            System.Diagnostics.Trace.WriteLine(ST.ElapsedMilliseconds);
        }
    }

}
4 голосов
/ 03 января 2011

Если не определены операторы статического преобразования, приведение должно занять примерно то же время.Потенциально возможны некоторые «встроенные» оптимизации при вызове метода для класса, а не для интерфейса, но это не будет замечено, если не вызывать метод безумно количество раз.

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

2 голосов
/ 03 января 2011

Прежде всего, вам не нужно приводить здесь, так как код должен работать без преобразования. IParent - это IEntity, поэтому он должен просто работать.

Влияет ли кастинг на производительность? Немного, если это включает преобразование (если тип реализует IConvertible и преобразование необходимо). В противном случае это незначительно , так как все, что ему нужно сделать, это сделать проверку типа , которая должна быть молниеносной.

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