Почему обработка List<int>
примерно в два раза быстрее, чем интерфейсы?
Отличный вопрос. При попытке foreach
что-то C # first проверяет, есть ли у типа коллекции уже метод с именем GetEnumerator
, который возвращает тип, который имеет MoveNext
и Current
. Если это так, вызывает их напрямую . Если нет, то он возвращается к использованию IEnumerable<T>
или IEnumerable
и IEnumerator<T>
или IEnumerator
для получения перечислителя, чтобы он мог вызывать MoveNext
и Current
.
Этот выбор дизайнабыл сделан по двум причинам. Во-первых, в мире C # 1.0 до появления дженериков это означало, что вы можете вызвать Current
, который вернул int
;IEnumerator.Current
- это, конечно, object
, поэтому можно указать int
, что является как скоростью, так и потерей памяти. Во-вторых, это означало, что авторы коллекций могли проводить эксперименты, чтобы выяснить, какая реализация MoveNext
и Current
показала наилучшую производительность.
Разработчики List<T>
сделали именно это;если вы посмотрите GetEnumerator
на List<T>
, вы обнаружите что-то интересное: он возвращает изменяемый тип значения. Да, изменяемые типы значений считаются легко злоупотребляемой плохой практикой. Но поскольку 99,999% случаев использования этой перегрузки GetEnumerator
вызвано от вашего имени foreach
, в подавляющем большинстве случаев вы даже не замечаете, что для вас существует изменчивая ценность злоупотребления, и, следовательно, не злоупотребляйте ею.
(ПРИМЕЧАНИЕ. Вывод предыдущего абзаца не должен заключаться в том, чтобы «использовать изменяемые типы значений, потому что они быстрые». Выводом следует: понять схемы использования ваших пользователей и затем разработать безопасный, эффективныйинструмент, который отвечает их потребностям . Обычно изменяемый тип значений не является подходящим инструментом.)
В любом случае, если коротко, мы избегаем всевозможных виртуальных вызовов, проверок типов интерфейса и т. д. прямая привязка к методам с изменяемыми типами значений при итерации чего-либо, известного во время компиляции, как List<T>
.
Что нам следует возвращать из функции и как управлять кодом, если мы заботимся о производительности?
Если вы заботитесь о быстродействии, вам следует сконцентрироваться на самой медленной вещи вЭлектронная программа . Самая медленная вещь в вашей программе, вызывающая MoveNext
в коллекции? Если так, поздравляю, у вас очень быстрая программа;MoveNext
- следующая вещь для оптимизации. Но в этом случае на самом деле вы должны спросить: «Как мне избежать или задержать этот цикл полностью?»если вы находитесь в этой лодке.
Если MoveNext
не самая медленная вещь в программе, то кого волнует, будет ли она на несколько наносекунд медленнее в конкретной реализации? Верните тип, который логически самый близкий к тому, что хочет и нуждается вызывающий, и не беспокойтесь о крошечном штрафе.