Чи є вплив на продуктивність під час виклику ToList ()?


139

Чи використовується під час використання ToList()вплив на продуктивність, який потрібно враховувати?

Я писав запит на отримання файлів із каталогу, який є запитом:

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

Однак, оскільки List<>замість цього мені подобається працювати , я вирішив поставитись на ...

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

Отже, чи є якийсь вплив на продуктивність, який слід враховувати, приймаючи рішення про подібне перетворення - або лише його враховувати при роботі з великою кількістю файлів? Це незначне перетворення?


+1 зацікавлений дізнатися відповідь і тут. ІМХО , якщо додаток не є продуктивність критично, я думаю , що я завжди використовую List<T>на користь системи , T[]якщо це робить код більш логічним / читаним / ремонтопрігоден (якщо, звичайно , перетворення НЕ викликають помітні проблеми з продуктивністю , в цьому випадку я б переустановлення відвідайте його я здогадуюсь)
Сепстер

Створення списку з масиву повинно бути дуже дешевим.
леппі

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()тут.
Джорен

Відповіді:


178

IEnumerable.ToList()

Так, IEnumerable<T>.ToList()чи має вплив на продуктивність, це O (n) операція, хоча, ймовірно, потребуватиме уваги лише в критичних операціях.

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

Хочеться ще раз зазначити, що це змінить лише величезний список, копіювання фрагментів пам'яті - це досить швидка операція.

Зручна порада, AsпротиTo

Ви помітите, що в 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кожен елемент.
Юстінас Симанавічус

Важливо зазначити, що ви можете часто бачити ToList () як оманливо вимагаючу операцію. Це відбувається, коли ви створюєте IEnumerable <> througha LINQ-запит. запит linq побудований, але не виконується. виклик ToList () запустить запит, і тому здається, що ресурс є інтенсивним, але це інтенсивний запит, а не операція ToList () (Якщо це не дуже великий список)
dancer42

36

Чи є вплив на продуктивність під час виклику 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.


19

Чи є вплив на продуктивність під час виклику toList ()?

Так, є. Використання методу розширення Enumerable.ToList()побудує новий List<T>об’єкт із IEnumerable<T>колекції джерел, що, звичайно, має вплив на продуктивність.

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

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

Коли нове List<T>будується з одного IEnumerable<T>, є два випадки:

  1. Реалізація джерела колекції ICollection<T>: Потім 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<>структури, щоб, можливо, зробити вас більш продуктивними, або ваш алгоритм більш дружнім, або якусь іншу перевагу. В іншому випадку ви просто навмисно додаєте незначний хіт виконання, без жодної причини. У такому випадку, природно, ви не повинні цього робити! :)


4

ToList()створює новий Список і вкладає в нього елементи, що означає, що пов'язана вартість з виконанням ToList(). У випадку невеликої колекції це не буде дуже помітною вартістю, але наявність величезної колекції може спричинити успіх у випадку використання ToList.

Як правило, ви не повинні використовувати ToList (), якщо робота, яку ви робите, не може бути виконана без перетворення колекції в Список. Наприклад, якщо ви просто хочете переглядати колекцію, вам не потрібно виконувати ToList

Якщо ви виконуєте запити проти джерела даних, наприклад, Бази даних, використовуючи LINQ для SQL, то вартість виконання ToList набагато більша, тому що, коли ви використовуєте ToList з LINQ в 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
близько, 0(n)де nзагальна сума байтів займають рядки в оригінальній колекції, а не кількість елементів (точніше n = байт / розмір слова)
user1416420

@ user1416420 Я можу помилитися, але чому це? Що робити , якщо ці збори якого - то іншого типу (наприклад. bool, intІ т.д.)? Вам не потрібно робити копію кожного рядка в колекції. Ви просто додаєте їх до нового списку.
Оскар Медерос

все ще не має значення новий розподіл пам'яті та копіювання байтів - це те, що вбиває цей метод. Буль також займе 4 байти в .NET. Насправді кожна посилання на об'єкт у .NET становить щонайменше 8 байт, тому це досить повільно. перші 4 байти вказують на таблицю типів, а другі 4 байти вказують на значення або місце пам'яті, де знайти значення
user1416420

3

Враховуючи ефективність отримання списку файлів, ToList()це незначно. Але не дуже для інших сценаріїв. Це дійсно залежить від того, де ви його використовуєте.

  • Закликаючи масив, список чи іншу колекцію, ви створюєте копію колекції як List<T>. Продуктивність тут залежить від розміру списку. Ви повинні робити це, коли це дійсно необхідно.

    У вашому прикладі ви називаєте це на масиві. Він повторює масив і додає елементи по одному до новоствореного списку. Тож ефективність роботи залежить від кількості файлів.

  • При виклику на умовах IEnumerable<T>, ви матеріалізувати в IEnumerable<T>(зазвичай запит).


2

ToList Створить новий список і скопіює елементи з оригінального джерела до новоствореного списку, так що єдине, що потрібно скопіювати, елементи з початкового джерела, і залежить від розміру джерела

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.