Возникает ли эффект при вызове ToList()?
При использовании ToList()
существует ли влияние производительности, которое необходимо учитывать?
Я писал запрос для извлечения файлов из каталога, который является запросом:
string[] imageArray = Directory.GetFiles(directory);
Однако, поскольку мне нравится работать с List<>
вместо этого, я решил включить...
List<string> imageList = Directory.GetFiles(directory).ToList();
Итак, есть ли какое-то влияние на производительность, которое следует учитывать при принятии решения сделать такое преобразование - или только для рассмотрения при работе с большим количеством файлов? Является ли это незначительным преобразованием?
Ответы
Ответ 1
IEnumerable.ToList()
Да, IEnumerable<T>.ToList()
оказывает влияние на производительность, это операция O (n), хотя, скорее всего, это потребует внимания только производительности критические операции.
Операция ToList()
будет использовать конструктор List(IEnumerable<T> collection)
. Этот конструктор должен сделать копию массива (в общем случае IEnumerable<T>
), иначе будущие модификации исходного массива будут меняться и на источнике T[]
, что было бы нежелательно в целом.
Я хотел бы повторить, что это только изменит ситуацию с огромным списком, копирование кусков памяти - довольно быстрая операция для выполнения.
Удобный наконечник, As
vs To
Вы заметите, что в LINQ существует несколько методов, начинающихся с As
(таких как AsEnumerable()
) и To
( например ToList()
). Для методов, начинающихся с To
, требуется преобразование, подобное приведенному выше (т.е. Может повлиять на производительность), а методы, начинающиеся с As
, не требуют и просто потребуют некоторой простой или простой операции.
Дополнительная информация о List<T>
Ниже приведена подробная информация о том, как List<T>
работает в случае, если вам интересно:
A List<T>
также использует конструкцию, называемую динамическим массивом, которую необходимо изменить по требованию, это событие resize копирует содержимое старого массива в новый массив. Таким образом, он начинает с малого и увеличивает размер, если требуется.
В этом разница между Capacity
и Count
на List<T>
. Capacity
относится к размеру массива за кулисами, Count
- количество элементов в List<T>
, которое всегда <= Capacity
. Поэтому, когда элемент добавляется в список, увеличивая его за Capacity
, размер List<T>
удваивается и массив копируется.
Ответ 2
Возникает ли влияние производительности при вызове toList()?
Да, конечно. Теоретически даже i++
влияет на производительность, он замедляет программу, возможно, несколько тиков.
Что делает .ToList
?
При вызове .ToList
код вызывает Enumerable.ToList()
, который является методом расширения, который return new List<TSource>(source)
. В соответствующем конструкторе, в худшем случае, он проходит через контейнер товаров и добавляет их один за другим в новый контейнер. Поэтому его поведение мало влияет на производительность. Невозможно быть горлом для бутылок с производительностью вашего приложения.
Что не так с кодом в вопросе
Directory.GetFiles
проходит через папку и сразу же возвращает имена всех файлов в память, у нее есть потенциальный риск того, что строка [] стоит много памяти, замедляя все.
Что следует делать тогда
Это зависит. Если вы (а также ваша бизнес-логика) гарантируете, что количество файлов в папке всегда невелико, код является приемлемым. Но он все же предложил использовать ленивую версию: Directory.EnumerateFiles
в С# 4. Это больше похоже на запрос, который не будет выполняться немедленно, вы можете добавить к нему больше запросов, например:
Directory.EnumerateFiles(myPath).Any(s => s.Contains("myfile"))
который перестанет искать путь, как только будет найден файл, имя которого содержит "myfile". Очевидно, что она имеет более высокую производительность, чем .GetFiles
.
Ответ 3
Возникает ли влияние производительности при вызове toList()?
Да, есть. Использование метода расширения Enumerable.ToList()
построит новый объект List<T>
из исходной коллекции IEnumerable<T>
, который, конечно, имеет влияние на производительность.
Однако понимание List<T>
может помочь вам определить значимость влияния производительности.
List<T>
использует массив (T[]
) для хранения элементов списка. Массивы не могут быть расширены после их выделения, поэтому List<T>
будет использовать массив избыточного размера для хранения элементов списка. Когда List<T>
растет за пределами размера базового массива, необходимо выделить новый массив, а содержимое старого массива нужно скопировать в новый массив большего размера, чтобы список мог расти.
Когда новый List<T>
построен из IEnumerable<T>
, существует два случая:
-
Исходная коллекция реализует ICollection<T>
: Затем ICollection<T>.Count
используется для получения точного размера исходной коллекции, а соответствующий массив поддержки распределяется до того, как все элементы исходной коллекции будут скопированы в массив подстановки, используя ICollection<T>.CopyTo()
. Эта операция довольно эффективна и, вероятно, будет отображать некоторую инструкцию процессора для копирования блоков памяти. Тем не менее, с точки зрения производительности требуется память для нового массива, а для копирования всех элементов требуются циклы процессора.
-
В противном случае размер исходной коллекции неизвестен, а перечислитель IEnumerable<T>
используется для добавления каждого элемента источника по одному к новому List<T>
. Первоначально массив подложки пуст и создается массив размером 4. Затем, когда этот массив слишком мал, размер удваивается, поэтому массив подкачки растет как 4, 8, 16, 32 и т.д. Каждый раз, когда массив поддержки поддерживается, он должен быть перераспределен, и все сохраненные до сих пор элементы должны быть скопированы. Эта операция намного дороже по сравнению с первым случаем, когда сразу можно создать массив правильного размера.
Кроме того, если в вашей исходной коллекции указано 33 элемента, список завершится использованием массива из 64 элементов, теряющих память.
В вашем случае исходная коллекция представляет собой массив, который реализует ICollection<T>
, поэтому влияние производительности не является чем-то, о чем вы должны беспокоиться, если ваш исходный массив не очень большой. Вызов ToList()
будет просто скопировать исходный массив и обернуть его в объект List<T>
. Даже производительность второго случая не стоит беспокоиться о небольших коллекциях.
Ответ 4
"есть ли влияние производительности, которое необходимо учитывать?"
Проблема с вашим точным сценарием заключается в том, что в первую очередь ваша реальная озабоченность по поводу производительности будет заключаться в скорости жесткого диска и эффективности кеша накопителя.
С этой точки зрения влияние, безусловно, незначительно, так как НЕТ не нужно учитывать.
НО ТОЛЬКО, если вам действительно нужны функции структуры List<>
, чтобы, возможно, сделать вас более продуктивными, или ваш алгоритм более дружелюбный, или какое-то другое преимущество. В противном случае вы просто намеренно добавляете незначительный удар производительности, без всякой причины. В этом случае, естественно, вы не должны этого делать!:)
Ответ 5
ToList()
создает новый список и помещает в него элементы, что означает, что с помощью ToList()
есть связанные затраты. В случае небольшой коллекции это будет не очень заметная стоимость, но наличие огромной коллекции может привести к поражению производительности при использовании ToList.
Как правило, вы не должны использовать ToList(), если работа, которую вы выполняете, не может быть выполнена без преобразования коллекции в список. Например, если вы просто хотите итерации по коллекции, вам не нужно выполнять ToList
Если вы выполняете запросы к источнику данных, например к базе данных с использованием LINQ to SQL, то стоимость выполнения ToList намного больше, потому что когда вы используете ToList с LINQ to SQL вместо выполнения Delayed Execution, т.е. загружаете элементы, когда это необходимо (что может быть полезным во многих сценариях), он мгновенно загружает элементы из базы данных в память
Ответ 6
Учитывая производительность поиска списка файлов, ToList()
является незначительным. Но не для других сценариев. Это действительно зависит от того, где вы его используете.
-
При вызове массива, списка или другой коллекции вы создаете копию коллекции как List<T>
. Производительность здесь зависит от размера списка. Вы должны сделать это, когда это действительно необходимо.
В вашем примере вы вызываете его в массиве. Он выполняет итерацию по массиву и добавляет элементы по одному в только что созданный список. Таким образом, влияние производительности зависит от количества файлов.
-
При вызове IEnumerable<T>
вы материализуете IEnumerable<T>
(обычно запрос).
Ответ 7
ToList Создает новый список и копирует элементы из исходного источника в только что созданный список, так что единственное, что нужно для копирования элементов из исходного источника и зависит от размера источника
Ответ 8
Он будет таким же эффективным, как и:
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
, чтобы сделать копию каждого элемента.