Я часто сталкиваюсь со случаем, когда хочу проверить запрос именно там, где я его объявляю. Обычно это происходит потому, что мне нужно многократно повторять его, а вычисление обходится дорого. Например:
string raw = "...";
var lines = (from l in raw.Split('\n')
let ll = l.Trim()
where !string.IsNullOrEmpty(ll)
select ll).ToList();
Это отлично работает. Но если я не собираюсь изменять результат, я мог бы также позвонить ToArray()
вместоToList()
.
Интересно, однако, ToArray()
реализуется ли это при первом вызове ToList()
и, следовательно, менее эффективно использует память, чем просто вызовToList()
.
Я сумасшедший? Должен ли я просто позвонить ToArray()
- безопасно и надежно, зная, что память не будет выделяться дважды?
.net
linq
performance
Фрэнк Крюгер
источник
источник
Ответы:
Если вам просто не нужен массив для удовлетворения других ограничений, которые вы должны использовать
ToList
. В большинстве сценариевToArray
выделяется больше памяти, чемToList
.Оба используют массивы для хранения, но
ToList
имеют более гибкое ограничение. Массив должен быть не меньше, чем количество элементов в коллекции. Если массив больше, это не проблема. ОднакоToArray
размер массива должен соответствовать количеству элементов.Чтобы удовлетворить это ограничение,
ToArray
часто делается еще одно распределение, чемToList
. Как только у него есть достаточно большой массив, он выделяет массив, который имеет точный размер, и копирует элементы обратно в этот массив. Единственный раз, когда этого можно избежать, это когда алгоритм увеличения массива просто совпадает с количеством элементов, которые необходимо сохранить (определенно в меньшинстве).РЕДАКТИРОВАТЬ
Несколько человек спросили меня о последствиях наличия дополнительной неиспользуемой памяти в
List<T>
значении.Это действительная проблема. Если созданная коллекция является долгоживущей, никогда не модифицируется после создания и имеет высокий шанс попадания в кучу Gen2, тогда вам лучше взять дополнительное выделение
ToArray
заранее.В целом, хотя я нахожу это более редким случаем. В этом случае гораздо чаще можно увидеть множество
ToArray
вызовов, которые сразу же передаются другим недолгим использованиям памяти.ToList
это явно лучше.Ключ здесь, чтобы профиль, профиль, а затем профиль еще.
источник
ToArray
можно выделить больше памяти, если ей нужен точный размер местоположений, где,ToList<>
очевидно, есть автоматические запасные местоположения. (Разница в производительности будет незначительной, поскольку
List<T>
реализована в виде динамически изменяемого массива. ВызовToArray()
(который использует внутреннийBuffer<T>
класс для увеличения массива) илиToList()
(который вызываетList<T>(IEnumerable<T>)
конструктор) в конечном итоге станет вопросом помещения их в массив и увеличения массива до тех пор, пока он не будет соответствовать всем им.Если вы хотите получить конкретное подтверждение этого факта, проверьте реализацию соответствующих методов в Reflector - вы увидите, что они сводятся к почти идентичному коду.
источник
ToArray()
иToList()
заключается в том, что первый должен обрезать избыток, который включает в себя копирование всего массива, тогда как последний не обрезает избыток, а использует в среднем 25 % больше памяти. Это будет иметь последствия, только если тип данных большойstruct
. Просто пища для размышлений.ToList
илиToArray
начнется с создания небольшого буфера. Когда этот буфер заполнен, он удваивает емкость буфера и продолжает работу. Поскольку емкость всегда удваивается, неиспользуемый буфер всегда будет между 0% и 50%.List
и другоеBuffer
проверитICollection
, в этом случае производительность будет одинаковой.(семь лет спустя ...)
Несколько других (хороших) ответов были сосредоточены на микроскопических различиях в производительности.
Этот пост является лишь дополнением, чтобы упомянуть семантическое различие, которое существует между
IEnumerator<T>
созданным array (T[]
) по сравнению с возвращаемымList<T>
.Лучше всего иллюстрируется на примере:
Приведенный выше код будет работать без исключения и выдает результат:
Это показывает, что
IEnumarator<int>
возвращаемый объектint[]
не отслеживает, был ли массив изменен с момента создания перечислителя.Обратите внимание, что я объявил локальную переменную
source
какIList<int>
. Таким образом, я убедился, что компилятор C # не оптимизируетforeach
оператор в нечто, эквивалентноеfor (var idx = 0; idx < source.Length; idx++) { /* ... */ }
циклу. Это то, что компилятор C # может сделать, если я используюvar source = ...;
вместо этого. В моей текущей версии .NET Framework фактически используемый здесь перечислитель является непубличным ссылочным типом,System.SZArrayHelper+SZGenericArrayEnumerator`1[System.Int32]
но, конечно, это деталь реализации.Теперь, если я меняю
.ToArray()
в.ToList()
, я получаю только:сопровождаемый
System.InvalidOperationException
взрывной поговоркой:Основным перечислителем в этом случае является общедоступный изменяемый тип-значение
System.Collections.Generic.List`1+Enumerator[System.Int32]
(IEnumerator<int>
в данном случае заключенный в рамку, потому что я используюIList<int>
).В заключение перечислитель, созданный с помощью
List<T>
отслеживания, меняет ли список во время перечисления, в то время как произведенный перечислителемT[]
нет. Поэтому учитывайте эту разницу при выборе между.ToList()
и.ToArray()
.Люди часто добавляют одну дополнительную
.ToArray()
или.ToList()
обходят коллекцию, которая отслеживает, была ли она изменена в течение жизни счетчика.(Если кто -то хочет знать , как
List<>
отслеживает от того, был ли изменен сбор, есть частные поля_version
в этом классе , который изменяется каждый разList<>
обновляется.)источник
Я согласен с @mquander, что разница в производительности должна быть незначительной. Тем не менее, я хотел проверить это, чтобы быть уверенным, поэтому я сделал - и это незначительно.
Каждый исходный массив / список имел 1000 элементов. Таким образом, вы можете видеть, что разница во времени и в памяти незначительна.
Мой вывод: вы также можете использовать ToList () , поскольку a
List<T>
предоставляет больше функциональных возможностей, чем массив, если только для вас не имеют значения несколько байт памяти.источник
struct
вместо примитивного типа или класса.ToList
или,ToArray
а не перечисление любогоIEnumerable
. List <T> .ToList () по-прежнему создает новый List <T> - он не просто «возвращает это».ToArray()
иToList()
слишком сильно отличаются, когда они снабженыICollection<T>
параметром - они просто выполняют одно выделение и одну операцию копирования. И то,List<T>
и другоеArray
реализуетICollection<T>
, поэтому ваши тесты вообще не действительны..Select(i => i)
чтобы избежатьICollection<T>
проблем с реализацией, и включает контрольную группу, чтобы увидеть, сколько времени уходит на перебор источникаIEnumerable<>
в первую очередь.ToList()
обычно предпочтительнее, если вы используете егоIEnumerable<T>
(например, из ORM). Если длина последовательности не известна в начале,ToArray()
создает коллекцию динамической длины, такую как List, а затем преобразует ее в массив, что занимает дополнительное время.источник
Enumerable.ToArray()
звонкиnew Buffer<TSource>(source).ToArray()
. В конструкторе Buffer, если источник реализует ICollection, он вызывает source.CopyTo (items, 0), а затем .ToArray () возвращает массив внутренних элементов напрямую. Таким образом, в этом случае нет преобразования, которое требует дополнительного времени. Если источник не реализует ICollection, то ToArray приведет к копированию массива, чтобы обрезать лишние неиспользуемые местоположения в конце массива, как описано выше в комментарии Скотта Риппи.Память всегда будет выделяться дважды - или что-то близкое к этому. Поскольку вы не можете изменить размер массива, оба метода будут использовать какой-то механизм для сбора данных в растущей коллекции. (Ну, список сам по себе является растущей коллекцией.)
Список использует массив в качестве внутреннего хранилища и удваивает емкость при необходимости. Это означает, что в среднем 2/3 предметов было перераспределено по меньшей мере один раз, половина из них перераспределена по меньшей мере дважды, половина - по меньшей мере трижды и т. Д. Это означает, что каждый элемент в среднем был перераспределен в 1,3 раза, что не сильно увеличивает накладные расходы.
Помните также, что если вы собираете строки, сама коллекция содержит только ссылки на строки, сами строки не перераспределяются.
источник
Сейчас 2020, и все используют .NET Core 3.1, поэтому я решил запустить некоторые тесты с Benchmark.NET.
TL; DR: ToArray () лучше с точки зрения производительности и лучше передает намерение, если вы не планируете изменять коллекцию.
Результаты:
источник
ToImmutableArray()
(из пакета System.Collections.Immutable) ArtРедактировать : последняя часть этого ответа недействительна. Впрочем, остальное пока полезная информация, поэтому я ее оставлю.
Я знаю, что это старый пост, но после того же вопроса и исследования я нашел кое-что интересное, чем стоит поделиться.
Во-первых, я согласен с @mquander и его ответом. Он прав, говоря, что с точки зрения производительности, они идентичны.
Тем не менее, я использовал Reflector, чтобы взглянуть на методы в
System.Linq.Enumerable
пространстве имен расширений, и заметил очень распространенную оптимизацию.Когда это возможно,
IEnumerable<T>
источник приводитсяIList<T>
илиICollection<T>
оптимизирует метод. Например, посмотрите наElementAt(int)
.Интересно, что Microsoft решила оптимизировать только для
IList<T>
, но не дляIList
. Похоже, Microsoft предпочитает использоватьIList<T>
интерфейс.System.Array
только реализуетIList
, поэтому он не выиграет ни от одной из этих оптимизаций расширения.Поэтому я утверждаю, что наилучшей практикой является использование
.ToList()
метода.Если вы используете какой-либо из методов расширения или передаете список другому методу, есть вероятность, что он может быть оптимизирован для
IList<T>
.источник
Я обнаружил, что других эталонных тестов, которые люди здесь сделали, не хватает, так что вот мой недостаток. Дайте мне знать, если вы нашли что-то не так с моей методологией.
Вы можете скачать скрипт LINQPad здесь .
Результаты:
Изменяя код выше, вы обнаружите, что:
int
s, а неstring
s.struct
s вместоstring
s обычно занимает намного больше времени, но на самом деле не сильно меняет соотношение.Это согласуется с выводами ответов с наибольшим количеством голосов:
ToList()
постоянно работает быстрее и будет лучшим выбором, если вы не планируете долго держаться за результаты.Обновить
@JonHanna указала, что в зависимости от реализации
Select
для реализацииToList()
или может быть возможноToArray()
заранее предсказать размер результирующей коллекции. Замена.Select(i => i)
в приведенном выше кодеWhere(i => true)
дает очень похожие результаты на данный момент, и, скорее всего, это будет сделано независимо от реализации .NET.источник
100000
и использует его для оптимизации обоих,ToList()
иToArray()
,ToArray()
будучи немного более легким, потому что ему не нужна операция сжатия, в которой он нуждался бы в противном случае, это единственное место, котороеToList()
имеет преимущество. Пример в вопросе все равно проиграл бы, потому чтоWhere
средства такого предсказания размера не могут быть сделаны..Select(i => i)
может быть заменено,.Where(i => true)
чтобы исправить это.ToArray()
преимущество), так и размер, который не соответствует описанному выше, и сравнивать результаты.ToArray()
прежнему проигрывает в лучшем случае. СMath.Pow(2, 15)
элементами это (ToList: 700мс, ToArray: 900мс). Добавление еще одного элемента увеличивает его (ToList: 925, ToArray: 1350). Интересно,ToArray
все еще копирует массив, даже когда он уже имеет идеальный размер? Они, вероятно, полагали, что это было достаточно редкое явление, которое не стоило дополнительных условий.Вы должны основывать свое решение на том
ToList
илиToArray
ином выборе дизайна. Если вы хотите коллекцию, которая может быть повторена и доступна только по индексу, выберитеToArray
. Если вам нужны дополнительные возможности добавления и удаления из коллекции позже без особых хлопот, сделайте этоToList
(на самом деле вы не можете добавить массив, но обычно это не самый подходящий инструмент для него).Если производительность имеет значение, вы должны также подумать, что будет быстрее работать. Реально, вы не будете звонить
ToList
илиToArray
миллион раз, но можете работать с полученным сбором миллион раз. В этом отношении[]
лучше, такList<>
как[]
с некоторыми накладными расходами. Посмотрите эту ветку для сравнения эффективности: какая из них более эффективна: List <int> или int []В моих собственных тестах некоторое время назад я нашел
ToArray
быстрее. И я не уверен, насколько искажены были тесты. Разница в производительности настолько незначительна, что заметна, только если вы выполняете эти запросы в цикле миллионы раз.источник
Очень поздний ответ, но я думаю, что он будет полезен для Google.
Они оба отстой, когда они созданы с помощью linq. Они оба реализуют один и тот же код для изменения размера буфера, если это необходимо .
ToArray
внутренне использует класс для преобразованияIEnumerable<>
в массив, выделяя массив из 4 элементов. Если этого недостаточно, он удваивает размер, создавая новый массив, удваивая размер текущего и копируя в него текущий массив. В конце он выделяет новый массив количества ваших предметов. Если ваш запрос возвращает 129 элементов, то ToArray сделает 6 выделений и операций копирования памяти, чтобы создать массив из 256 элементов, а затем - еще один массив из 129, который нужно вернуть. так много для эффективности памяти.ToList делает то же самое, но пропускает последнее распределение, так как вы можете добавлять элементы в будущем. Список не заботится, создан ли он из запроса linq или создан вручную.
для создания List лучше с памятью, но хуже с процессором, так как list - универсальное решение, каждое действие требует проверки диапазона в дополнение к внутренней проверке диапазона .net для массивов.
Поэтому, если вы будете повторять свой набор результатов слишком много раз, тогда массивы хороши, поскольку это означает меньше проверок диапазона, чем списки, а компиляторы обычно оптимизируют массивы для последовательного доступа.
Распределение инициализации списка может быть лучше, если вы укажете параметр емкости при его создании. В этом случае он будет выделять массив только один раз, при условии, что вы знаете размер результата.
ToList
В linq не указана перегрузка для ее предоставления, поэтому мы должны создать наш метод расширения, который создает список с заданной емкостью, а затем используетList<>.AddRange
.Чтобы закончить этот ответ, я должен написать следующие предложения
источник
List<T>
, но когда вы этого не сделаете или не сможете, вы не сможете ничего с этим поделать.Это старый вопрос - но для удобства пользователей, которые сталкиваются с ним, существует также и альтернатива «Memoizing» для Enumerable - который приводит к кешированию и прекращению многократного перечисления оператора Linq, чем и является ToArray () и ToList () используются много, хотя атрибуты коллекции списка или массива никогда не используются.
Memoize доступен в библиотеке RX / System.Interactive и объясняется здесь: Больше LINQ с System.Interactive
(Из блога Барта де Смета, который настоятельно рекомендуется прочитать, если вы много работаете с Linq to Objects)
источник
Один из вариантов - добавить собственный метод расширения, который возвращает только для чтения
ICollection<T>
. Это может быть лучше, чем использование,ToList
илиToArray
когда вы не хотите использовать либо свойства индексации массива / списка, либо добавлять / удалять из списка.Модульные тесты:
источник
ToListAsync<T>()
является предпочтительным.В Entity Framework 6 оба метода в конечном итоге вызывают один и тот же внутренний метод, но
ToArrayAsync<T>()
вызываютlist.ToArray()
в конце, который реализован какТак что
ToArrayAsync<T>()
имеет некоторые накладные расходы, поэтомуToListAsync<T>()
является предпочтительным.источник
Старый вопрос, но новые вопросы всегда.
Согласно источнику System.Linq.Enumerable ,
ToList
просто верните anew List(source)
, аToArray
используйте anew Buffer<T>(source).ToArray()
для возврата aT[]
.При работе на
IEnumerable<T>
единственном объектеToArray
выделяйте память еще один раз, чемToList
. Но вам не нужно заботиться об этом в большинстве случаев, потому что GC будет выполнять сборку мусора при необходимости.Те, кто задают этот вопрос, могут запустить следующий код на своей машине, и вы получите ответ.
Я получил эти результаты на моей машине:
Из-за ограничения на количество символов в ответе stackoverflow образцы списков Group2 и Group3 опущены.
Как вы можете видеть, это действительно не важно использовать
ToList
илиToArry
в большинстве случаев.При обработке вычисляемых во время выполнения
IEnumerable<T>
объектов, если нагрузка, вызванная вычислениями, больше, чем выделение памяти и операции копированияToList
иToArray
, несоответствие незначительно (C.ToList vs C.ToArray
иS.ToList vs S.ToArray
).Разница может наблюдаться только для не рассчитанных во время выполнения
IEnumerable<T>
объектов (C1.ToList vs C1.ToArray
иS1.ToList vs S1.ToArray
). Но абсолютная разница (<60 мс) все еще приемлема для одного миллиона маленьких объектовIEnumerable<T>
. В самом деле, разница решается реализацииEnumerator<T>
изIEnumerable<T>
. Так что, если ваша программа действительно очень чувствительна к этому, вы должны профиль, профиль, профиль ! Наконец, вы, вероятно, обнаружите, что узкое место не вToList
илиToArray
, но в деталях счетчиков.И, результат
C2.ToList vs C2.ToArray
иS2.ToList vs S2.ToArray
показывает, что вам действительно не нужно заботитьсяToList
илиToArray
обICollection<T>
объектах, не рассчитанных во время выполнения .Конечно, это всего лишь результаты на моей машине, фактическое время выполнения этих операций на другой машине не будет одинаковым, вы можете узнать на своей машине, используя приведенный выше код.
Единственная причина, по которой вам нужно сделать выбор, заключается в том, что у вас есть особые потребности
List<T>
илиT[]
, как описано в ответе @Jeppe Stig Nielsen .источник
Для тех, кто заинтересован в использовании этого результата в другом Linq-to-sql, таких как
тогда генерируемый SQL будет одинаковым независимо от того, использовали ли вы List или Array для myListOrArray. Теперь я знаю, что некоторые могут спросить, почему даже перечислять перед этим оператором, но есть разница между SQL, сгенерированным из IQueryable vs (List или Array).
источник