Возникает ли эффект при вызове 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, чтобы сделать копию каждого элемента.