Влияние производительности на универсальные интерфейсы
Я работаю над приложениями, разработанными в С#/.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() перед передачей параметра, но вы будете уверены, что там нет скрытых различимых оговорок.