Влияет ли на производительность при вызове ToList ()?

142

При использовании ToList(), есть влияние на производительность , что нужно учитывать?

Я писал запрос для извлечения файлов из каталога, который представляет собой запрос:

string[] imageArray = Directory.GetFiles(directory);

Однако, поскольку мне нравится работать List<>, я решил добавить ...

List<string> imageList = Directory.GetFiles(directory).ToList();

Итак, есть ли какое-то влияние на производительность, которое следует учитывать при принятии решения о таком преобразовании - или его следует учитывать только при работе с большим количеством файлов? Это незначительное преобразование?

Коди
источник
+1 интересно узнать ответ и здесь. ИМХО, если приложение не критично к производительности, я думаю, что всегда буду использовать a List<T>в пользу a, T[]если это сделает код более логичным / читаемым / поддерживаемым (если, конечно, преобразование не вызывает заметных проблем с производительностью, и в этом случае я бы повторно посети это я думаю).
Sepster
Создание списка из массива должно быть очень дешевым.
leppie
2
@Sepster Я указываю только тип данных настолько конкретно, насколько мне нужно для работы. Если мне не нужно звонить Addили Remove, я бы оставил это как IEnumerable<T>(или даже лучше var)
pswg
4
Думаю, в этом случае лучше вызвать EnumerateFilesвместо GetFiles, чтобы был создан только один массив.
tukaef
3
GetFiles(directory), поскольку он реализован в .NET в настоящее время, в значительной степени делает new List<string>(EnumerateFiles(directory)).ToArray(). Таким образом GetFiles(directory).ToList()создается список, из него создается массив, а затем снова создается список. Как говорит 2kay, вам лучше делать это EnumerateFiles(directory).ToList()здесь.
Joren

Ответы:

183

IEnumerable.ToList()

Да, IEnumerable<T>.ToList()влияет на производительность, это операция O (n), хотя она, вероятно, потребует внимания только в критических для производительности операциях.

В ToList()операции будет использоваться List(IEnumerable<T> collection)конструктор. Этот конструктор должен делать копию массива (в более общем смысле IEnumerable<T>), в противном случае будущие модификации исходного массива также изменятся в источнике, T[]что в целом нежелательно.

Я хотел бы повторить, что это будет иметь значение только для огромного списка, копирование фрагментов памяти - довольно быстрая операция.

Подсказка, AsvsTo

Вы заметите, что в LINQ есть несколько методов, которые начинаются с As(например, AsEnumerable()) и To(например, ToList()). Методы, которые начинаются с, Toтребуют преобразования, как указано выше (т. Е. Могут повлиять на производительность), а методы, которые начинаются с As, не требуют и просто требуют некоторого приведения или простой операции.

Дополнительная информация о List<T>

Вот еще немного подробностей о том, как List<T>работает, если вам интересно :)

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

Это разница между Capacityи Countатрибутами на List<T>. Capacityотносится к размеру скрытого массива, Countэто количество элементов, List<T>которое всегда есть <= Capacity. Поэтому, когда элемент добавляется в список, увеличивая его Capacity, размер List<T>удваивается, и массив копируется.

Дэниел Иммс
источник
2
Я просто хотел подчеркнуть, что List(IEnumerable<T> collection)конструктор проверяет, есть ли параметр коллекции, ICollection<T>а затем сразу же создает новый внутренний массив с требуемым размером. Если набор параметров отсутствует ICollection<T>, конструктор выполняет итерацию по нему и вызывает Addкаждый элемент.
Justinas Simanavicius
Важно отметить, что вы часто можете рассматривать ToList () как вводящую в заблуждение операцию. Это происходит, когда вы создаете IEnumerable <> через запрос LINQ. запрос linq создается, но не выполняется. вызов ToList () запустит запрос и, следовательно, будет казаться ресурсоемким, но это запрос, который является интенсивным, а не операция ToList () (если только это не действительно огромный список)
dancer42
38

Есть ли влияние на производительность при вызове toList ()?

Да, конечно. Теоретически даже i++влияет на производительность, это замедляет программу, может быть, на несколько тактов.

Что .ToListделать?

Когда вы вызываете .ToList, код вызывает Enumerable.ToList()метод расширения, который return new List<TSource>(source). В соответствующем конструкторе, в худшем случае, он проходит через контейнер элементов и добавляет их один за другим в новый контейнер. Так что его поведение мало влияет на производительность. Невозможно быть узким местом в производительности вашего приложения.

Что не так с кодом в вопросе

Directory.GetFilesпроходит через папку и немедленно возвращает имена всех файлов в память, есть потенциальный риск, что строка [] будет стоить много памяти, замедляя все.

Что делать тогда

Это зависит. Если вы (как и ваша бизнес-логика) гарантируете, что количество файлов в папке всегда невелико, код приемлем. Но все же предлагается использовать ленивую версию: Directory.EnumerateFilesна C # 4. Это больше похоже на запрос, который не будет выполнен немедленно, вы можете добавить к нему дополнительный запрос, например:

Directory.EnumerateFiles(myPath).Any(s => s.Contains("myfile"))

который прекратит поиск пути, как только будет найден файл, имя которого содержит «myfile». Тогда это, очевидно, имеет лучшую производительность .GetFiles.

Ченг Чен
источник
20

Есть ли влияние на производительность при вызове toList ()?

Да, есть. Использование метода расширения Enumerable.ToList()создаст новый List<T>объект из IEnumerable<T>исходной коллекции, что, конечно же, повлияет на производительность.

Однако понимание List<T>может помочь вам определить, является ли влияние на производительность значительным.

List<T>использует array ( T[]) для хранения элементов списка. List<T>После выделения массивы не могут быть расширены, поэтому для хранения элементов списка будет использоваться массив слишком большого размера. Когда List<T>размер превышает размер базового массива, необходимо выделить новый массив, а содержимое старого массива необходимо скопировать в новый более крупный массив, прежде чем список сможет вырасти.

Когда создается новый List<T>объект IEnumerable<T>, возможны два случая:

  1. Исходная коллекция реализует ICollection<T>: Then ICollection<T>.Countиспользуется для получения точного размера исходной коллекции, и соответствующий резервный массив выделяется до того, как все элементы исходной коллекции будут скопированы в резервный массив с использованием ICollection<T>.CopyTo(). Эта операция довольно эффективна и, вероятно, будет отображаться в какой-нибудь инструкции ЦП для копирования блоков памяти. Однако с точки зрения производительности для нового массива требуется память, а для копирования всех элементов требуются циклы ЦП.

  2. В противном случае размер исходной коллекции неизвестен, и перечислитель IEnumerable<T>используется для добавления каждого исходного элемента по одному к новому List<T>. Первоначально резервный массив пуст, и создается массив размером 4. Затем, когда этот массив слишком мал, его размер удваивается, поэтому резервный массив растет как этот 4, 8, 16, 32 и т. Д. Каждый раз, когда резервный массив растет, его необходимо перераспределить, и все элементы, сохраненные до сих пор, должны быть скопированы. Эта операция намного дороже по сравнению с первым случаем, когда массив правильного размера может быть создан сразу.

    Кроме того, если ваша исходная коллекция содержит, скажем, 33 элемента, список будет использовать массив из 64 элементов, тратя немного памяти.

В вашем случае исходная коллекция представляет собой массив, который реализует, ICollection<T>поэтому влияние на производительность не является тем, о чем вы должны беспокоиться, если ваш исходный массив не очень велик. Вызов ToList()просто скопирует исходный массив и обернет его в List<T>объект. Даже производительность второго случая не является поводом для беспокойства для небольших коллекций.

Мартин Ливерсаж
источник
5

"есть ли влияние на производительность, которое необходимо учитывать?"

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

С этой точки зрения, влияние, безусловно, незначительно до такой степени, что НЕТ необходимости его рассматривать.

НО ТОЛЬКО в том случае, если вам действительно нужны функции List<>структуры, которые могут сделать вас более продуктивным, или ваш алгоритм более дружелюбным, или какое-то другое преимущество. В противном случае вы просто намеренно добавляете незначительное снижение производительности без всякой причины. В таком случае, естественно, делать этого не стоит! :)

Jross
источник
4

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

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

Если вы выполняете запросы к источнику данных, например, к базе данных с использованием LINQ to SQL, тогда стоимость выполнения ToList намного больше, потому что когда вы используете ToList с LINQ to SQL вместо выполнения отложенного выполнения, то есть загружаете элементы при необходимости (что может быть полезно во многих сценариях) он мгновенно загружает элементы из базы данных в память

Харис Хасан
источник
Харис: в чем я не уверен относительно первоисточника, что случится с первоисточником после вызова ToList ()
TalentTuner
@Saurabh GC уберет это
pswg
@Saurabh ничего не случится с первоисточником. На элементы первоисточника будут ссылки во вновь созданном списке
Харис Хасан
«если вы просто хотите перебирать коллекцию, вам не нужно выполнять ToList» - так как же вам выполнить итерацию?
SharpC
4

Это будет столь же (не) эффективно, как выполнение:

var list = new List<T>(items);

Если вы дизассемблируете исходный код конструктора, который принимает IEnumerable<T>, вы увидите, что он будет делать несколько вещей:

  • Вызов collection.Count, поэтому, если collectionесть IEnumerable<T>, он принудительно выполнит. Если collectionэто массив, список и т.д., то должно быть O(1).

  • Если collectionреализует ICollection<T>, он сохранит элементы во внутреннем массиве с помощью ICollection<T>.CopyToметода. Так и должно быть O(n), учитывая nдлину коллекции.

  • Если collectionне реализован ICollection<T>, он будет перебирать элементы коллекции и добавлять их во внутренний список.

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

Оскар Медерос
источник
3
close, 0(n)где n- общая сумма байтов, которые занимают строки в исходной коллекции, а не количество элементов (точнее, n = байты / размер слова)
user1416420
@ user1416420 Я могу ошибаться, но почему? Что делать , если это собрание какого - то другого типа (например. bool, intИ т.д.)? На самом деле вам не нужно делать копию каждой строки в коллекции. Вы просто добавляете их в новый список.
Oscar Mederos
по-прежнему не имеет значения новое распределение памяти и копирование байтов - вот что убивает этот метод. Логическое значение также будет занимать 4 байта в .NET. На самом деле каждая ссылка на объект в .NET имеет длину не менее 8 байт, поэтому это довольно медленно. первые 4 байта указывают на таблицу типов, а вторые 4 байта указывают на значение или место в памяти, где найти значение
user1416420
3

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

  • При вызове массива, списка или другой коллекции вы создаете копию коллекции как List<T>. Производительность здесь зависит от размера списка. Вы должны делать это, когда действительно необходимо.

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

  • При вызове на условиях IEnumerable<T>, вы материализовать в IEnumerable<T>(обычно запрос).

Мохаммад Дехган
источник
2

ToList создаст новый список и скопирует элементы из исходного источника во вновь созданный список, поэтому единственное, что нужно сделать, это скопировать элементы из исходного источника и зависит от размера источника.

ТалантТюнер
источник