Интерфейс Литье против класса Литье

17

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

Учитывая, что я хочу создать более правильную эвристику, когда дело доходит до программирования, мне было предложено задать этот вопрос для гуру .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? Если да, то на сколько? И, если вы знаете ответ, почему?

    
задан Squirrelsama 03.01.2011 в 16:38
источник

6 ответов

7

Взгляните сюда:

Ссылка

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

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

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

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 (поскольку бокс / unboxing задействован в последнем), но это не так: конкретный класс / структура Кажется, ссылка всегда быстрее.

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

    
ответ дан rsenna 03.01.2011 в 16:44
источник
16

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

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

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

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

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

    
ответ дан Eric Lippert 04.01.2011 в 00:32
источник
7

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

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

ответ дан Henk Holterman 03.01.2011 в 16:44
источник
4

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

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

    
ответ дан Marc Gravell 03.01.2011 в 16:50
источник
4

Вы пробовали проверить это? Вот цикл, который работает 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);
        }
    }

}
    
ответ дан Chris Haas 03.01.2011 в 17:36
источник
2

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

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

    
ответ дан Aliostad 03.01.2011 в 16:45
источник