Влияние производительности на универсальные интерфейсы

Я работаю над приложениями, разработанными в С#/.NET с Visual Studio. Очень часто ReSharper, в прототипах моих методов, советует мне заменить тип моих входных параметров более универсальными. Например, List < > с IEnumerable < > если я использую только список с foreach в теле моего метода. Я могу понять, почему это выглядит умнее, но я очень обеспокоен работой. Я боюсь, что производительность моих приложений уменьшится, если я послушаю ReSharper...

Может кто-нибудь объяснить мне (более или менее) то, что происходит за кулисами (то есть в CLR), когда я пишу:

public void myMethod(IEnumerable<string> list)
{
  foreach (string s in list)
  {
    Console.WriteLine(s);
  }
}

static void Main()
{
  List<string> list = new List<string>(new string[] {"a", "b", "c"});
  myMethod(list);
}

и в чем разница:

public void myMethod(List<string> list)
{
  foreach (string s in list)
  {
    Console.WriteLine(s);
  }
}

static void Main()
{
  List<string> list = new List<string>(new string[] {"a", "b", "c"});
  myMethod(list);
}

Ответ 1

Вы беспокоитесь о производительности - но есть ли у вас какие-либо основания для этой проблемы? Я предполагаю, что вы вообще не сравнили код. Всегда проверяйте перед заменой читаемого, чистого кода с более совершенным кодом.

В этом случае вызов Console.WriteLine будет в любом случае доминировать над производительностью.

Хотя я подозреваю, что здесь может быть теоретическое различие в производительности между использованием List<T> и IEnumerable<T>, я подозреваю, что число случаев, когда оно значимо в реальных приложениях, исчезает незначительно.

Это не так, как если бы тип последовательности использовался для многих операций - есть один вызов GetEnumerator(), который объявлен как возвращаемый IEnumerator<T> в любом случае. По мере того, как список становится больше, любая разница в производительности между ними будет еще меньше, поскольку она будет иметь какое-либо влияние вообще в самом начале цикла.

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

Что касается того, что происходит за кулисами - вам придется вникать в глубокие детали того, что именно в метаданных в каждом случае. Я подозреваю, что в случае интерфейса есть один дополнительный уровень перенаправления, по крайней мере теоретически - CLR должен был бы работать, где в типе целевого объекта vtable для IEnumerable<T> был, а затем вызывается в соответствующий код метода, В случае List<T>, JIT будет знать правильное смещение в vtable для начала, без дополнительного поиска. Это просто основано на моем несколько туманном понимании JITting, thunking, vtables и их применении к интерфейсам. Это может быть немного неправильным, но что более важно, это деталь реализации.

Ответ 2

Вам нужно будет с уверенностью смотреть на сгенерированный код, но в этом случае я сомневаюсь, что есть большая разница. Инструкция foreach всегда работает на IEnumerable или IEnumerable<T>. Даже если вы укажете List<T>, ему все равно придется получить IEnumerable<T> для повторения.

Ответ 3

В общем, я бы сказал, если вы замените эквивалентный не общий интерфейс на общий вкус (скажем IList<>IList<T>), вы обязательно получите лучшую или эквивалентную производительность.

Одной из уникальных точек продажи является то, что, в отличие от java,.NET не использует стирание типов и поддерживает истинные типы значений (struct), одно из основных отличий заключается в том, как он хранится, например. a List<int> внутренне. Это может довольно быстро стать большой разницей в зависимости от того, насколько интенсивно используется List.


Слепой синтетический бензин показал:

    for (int j=0; j<1000; j++)
    {
        List<int> list = new List<int>();
        for (int i = 1<<12; i>0; i--)
            list.Add(i);

        list.Sort();
    }

чтобы быть быстрее в 3.2x, чем полуэквивалентный не общий:

    for (int j=0; j<1000; j++)
    {
        ArrayList list = new ArrayList();
        for (int i = 1<<12; i>0; i--)
            list.Add(i);

        list.Sort();
    }

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

Ответ 4

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

Ответ 5

Основной причиной этой рекомендации является создание метода, который работает с IEnumberable vs. List, в будущем. Если в будущем вам нужно создать MySpecialStringsCollection, вы можете реализовать его методом IEnumerable и использовать тот же метод.

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

Ответ 6

В первой версии (IEnumerable) она более общая и фактически вы говорите, что метод принимает любой аргумент, реализующий этот интерфейс.

Вторая версия yo ограничивает метод принятия типа класса sepcific, и это не рекомендуется вообще. И производительность в основном одинакова.

Ответ 7

Определение для List<T>:

[SerializableAttribute]
public class List<T> : IList<T>, ICollection<T>, 
    IEnumerable<T>, IList, ICollection, IEnumerable

Итак, List<T> получается из IList, ICollection, IList<T>, и ICollection<T>, в дополнение к IEnumerable и IEnumerable<T>.

Интерфейс IEnumerable предоставляет метод GetEnumerator, который возвращает метод IEnumerator, a MoveNext и свойство Current. Эти механизмы - это то, что класс List<T> использует для итерации по списку с помощью foreach и next.

Из этого следует, что если IList, ICollection, IList<T>, and ICollection<T> не требуется выполнять задание, тогда разумно использовать IEnumerable или IEnumerable<T>, тем самым устраняя дополнительную сантехнику.

Ответ 8

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

Ответ 9

Для статического повышения не существует штрафа за производительность. Это логическая конструкция в тексте программы.

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

Ответ 10

Получение в IEnumerable < > может вызвать определенные проблемы, так как вы можете получить некоторое выражение LINQ с различным исполнением или вернуть доход. В обоих случаях у вас не будет коллекции, кроме того, что вы можете продолжить. Поэтому, когда вы хотите установить некоторые границы, вы можете запросить массив. Нет проблемы с вызовом collection.ToArray() перед передачей параметра, но вы будете уверены, что там нет скрытых различимых оговорок.