C'è un impatto sulle prestazioni quando si chiama ToList ()?


139

Durante l'utilizzo ToList(), è necessario considerare un impatto sulle prestazioni?

Stavo scrivendo una query per recuperare i file da una directory, che è la query:

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

Tuttavia, poiché mi piace lavorare con List<>, invece, ho deciso di inserire ...

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

Quindi, c'è una sorta di impatto sulle prestazioni che dovrebbe essere considerato quando si decide di fare una conversione come questa - o solo da considerare quando si ha a che fare con un numero elevato di file? È una conversione trascurabile?


+1 interessato a conoscere la risposta anche qui. IMHO a meno che l'applicazione è prestazioni critiche, penso che avrei sempre usare un List<T>a favore di un T[]se rende il codice più logico / leggibile / mantenibile (a meno che, naturalmente, la conversione è stata causando notevoli problemi di prestazioni nel qual caso avrei ri- visitalo immagino).
Sepster,

La creazione di un elenco da un array dovrebbe essere super economica.
leppie,

2
@Sepster Specifico solo il tipo di dati nello specifico in cui ho bisogno di fare un lavoro. Se non dovessi chiamare Addo Remove, lo lascerei come IEnumerable<T>(o anche meglio var)
pswg

4
Penso che in questo caso sia meglio chiamare EnumerateFilesinvece di GetFiles, quindi verrà creato un solo array.
tukaef,

3
GetFiles(directory), come è attualmente implementato in .NET, praticamente new List<string>(EnumerateFiles(directory)).ToArray(). Quindi GetFiles(directory).ToList()crea un elenco, crea un array da quello, quindi crea nuovamente un elenco. Come dice 2kay, dovresti preferire fare EnumerateFiles(directory).ToList()qui.
Joren,

Risposte:


178

IEnumerable.ToList()

Sì, IEnumerable<T>.ToList()ha un impatto sulle prestazioni, è un'operazione O (n) sebbene probabilmente richiederà attenzione solo nelle operazioni critiche per le prestazioni.

L' ToList()operazione utilizzerà il List(IEnumerable<T> collection)costruttore. Questo costruttore deve fare una copia dell'array (più in generale IEnumerable<T>), altrimenti le modifiche future dell'array originale cambieranno T[]anche sull'origine, cosa che non sarebbe auspicabile in generale.

Vorrei ribadire che questo farà la differenza solo con un enorme elenco, copiare blocchi di memoria è un'operazione abbastanza veloce da eseguire.

Consiglio pratico, AsvsTo

Noterai che in LINQ ci sono diversi metodi che iniziano con As(come AsEnumerable()) e To(come ToList()). I metodi che iniziano con Torichiedono una conversione come sopra (cioè possono influire sulle prestazioni), e i metodi che iniziano con Asnon richiedono e richiederanno solo alcune operazioni di cast o semplici operazioni.

Ulteriori dettagli su List<T>

Ecco un po 'più di dettaglio su come List<T>funziona nel caso ti interessi :)

A List<T>utilizza anche un costrutto chiamato array dinamico che deve essere ridimensionato su richiesta, questo evento di ridimensionamento copia il contenuto di un vecchio array nel nuovo array. Quindi inizia in piccolo e aumenta di dimensioni se necessario .

Questa è la differenza tra gli attributi Capacitye Countsu List<T>. Capacitysi riferisce alla dimensione dell'array dietro le quinte, Countè il numero di elementi in List<T>cui è sempre <= Capacity. Pertanto, quando un elemento viene aggiunto all'elenco, aumentandolo oltre Capacity, la dimensione di List<T>viene raddoppiata e l'array viene copiato.


2
Volevo solo sottolineare che il List(IEnumerable<T> collection)costruttore verifica se il parametro collection è ICollection<T>e quindi crea subito un nuovo array interno con le dimensioni richieste. Se la raccolta dei parametri non lo è ICollection<T>, il costruttore scorre attraverso di essa e richiede Addogni elemento.
Giustino Simanavicio,

È importante notare che spesso potresti vedere ToList () come un'operazione fuorviante. Ciò accade quando si crea una query LINQ IEnumerable <> througha. la query linq è costruita ma non eseguita. chiamando ToList () verrà eseguita la query e sembrerà quindi dispendioso in termini di risorse, ma è la query ad essere intensiva e non l'operazione ToList () (a meno che non sia un elenco davvero enorme)
ballerino42

36

C'è un impatto sulle prestazioni quando si chiama toList ()?

Sì, naturalmente. Teoricamente i++ha anche un impatto sulle prestazioni, rallenta il programma di forse qualche tick.

Cosa fa .ToList?

Quando invochi .ToList, il codice chiama Enumerable.ToList()quale è un metodo di estensione che return new List<TSource>(source). Nel costruttore corrispondente, nella peggiore circostanza, passa attraverso il contenitore degli articoli e li aggiunge uno per uno in un nuovo contenitore. Quindi il suo comportamento influisce poco sulle prestazioni. È impossibile essere un collo di bottiglia della tua applicazione.

Cosa c'è che non va nel codice nella domanda

Directory.GetFilespassa attraverso la cartella e restituisce immediatamente tutti i nomi dei file in memoria, ha il potenziale rischio che la stringa [] costi molta memoria, rallentando tutto.

Cosa dovrebbe essere fatto allora

Dipende. Se tu (così come la tua logica aziendale) garantisci che la quantità di file nella cartella è sempre piccola, il codice è accettabile. Ma è ancora suggerito di usare una versione pigra: Directory.EnumerateFilesin C # 4. Questo è molto più simile a una query, che non verrà eseguita immediatamente, è possibile aggiungere più query su di essa come:

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

che interromperà la ricerca del percorso non appena viene trovato un file il cui nome contiene "myfile". Questo ovviamente ha prestazioni migliori allora .GetFiles.


19

C'è un impatto sulle prestazioni quando si chiama toList ()?

Si C'è. L'uso del metodo di estensione Enumerable.ToList()costruirà un nuovo List<T>oggetto dalla IEnumerable<T>raccolta di origine che ovviamente ha un impatto sulle prestazioni.

Tuttavia, la comprensione List<T>può aiutarti a determinare se l'impatto sulle prestazioni è significativo.

List<T>utilizza un array ( T[]) per memorizzare gli elementi dell'elenco. Le matrici non possono essere estese una volta allocate, quindi List<T>utilizzerà una matrice di dimensioni eccessive per memorizzare gli elementi dell'elenco. Quando List<T>cresce oltre la dimensione dell'array sottostante, deve essere allocato un nuovo array e il contenuto del vecchio array deve essere copiato nel nuovo array più grande prima che l'elenco possa crescere.

Quando un nuovo List<T>è costruito da un IEnumerable<T>ci sono due casi:

  1. La raccolta di origine implementa ICollection<T>: Quindi ICollection<T>.Countviene utilizzata per ottenere le dimensioni esatte della raccolta di origine e viene allocata una matrice di supporto corrispondente prima che tutti gli elementi della raccolta di origine vengano copiati nella matrice di supporto mediante ICollection<T>.CopyTo(). Questa operazione è abbastanza efficiente e probabilmente verrà mappata su alcune istruzioni della CPU per la copia di blocchi di memoria. Tuttavia, in termini di prestazioni è necessaria la memoria per il nuovo array e sono necessari cicli CPU per la copia di tutti gli elementi.

  2. In caso contrario, la dimensione della raccolta di origine è sconosciuta e l'enumeratore di IEnumerable<T>viene utilizzato per aggiungere ogni elemento di origine uno alla volta al nuovo List<T>. Inizialmente l'array di supporto è vuoto e viene creato un array di dimensioni 4. Quindi, quando questo array è troppo piccolo, la dimensione viene raddoppiata in modo che l'array di supporto cresca in questo modo 4, 8, 16, 32 ecc. Ogni volta che l'array di supporto cresce, deve essere riallocato e tutti gli elementi archiviati finora devono essere copiati. Questa operazione è molto più costosa rispetto al primo caso in cui è possibile creare immediatamente un array della dimensione corretta.

    Inoltre, se la tua raccolta di sorgenti contiene diciamo 33 elementi, la lista finirà per usare una matrice di 64 elementi che sprecano un po 'di memoria.

Nel tuo caso, la raccolta di origine è un array che implementa, ICollection<T>quindi l'impatto sulle prestazioni non è qualcosa di cui dovresti preoccuparti a meno che l'array di origine non sia molto grande. La chiamata ToList()copierà semplicemente l'array di origine e lo avvolgerà in un List<T>oggetto. Anche l'esecuzione del secondo caso non è qualcosa di cui preoccuparsi per le piccole collezioni.


5

"c'è un impatto sulle prestazioni che deve essere considerato?"

Il problema con il tuo preciso scenario è che innanzitutto la tua vera preoccupazione per le prestazioni sarebbe dalla velocità del disco rigido e dall'efficienza della cache dell'unità.

Da quel punto di vista, l'impatto è sicuramente trascurabile al punto che NO non deve essere considerato.

MA SOLO se hai davvero bisogno delle caratteristiche della List<>struttura per renderti possibilmente più produttivo, o il tuo algoritmo più amichevole, o qualche altro vantaggio. Altrimenti, stai semplicemente aggiungendo di proposito un colpo di performance insignificante, senza motivo. Nel qual caso, naturalmente, non dovresti farlo! :)


4

ToList()crea un nuovo elenco e inserisce gli elementi, il che significa che esiste un costo associato con il fare ToList(). Nel caso di una piccola collezione non sarà un costo molto evidente, ma avere una vasta collezione può causare un calo delle prestazioni in caso di utilizzo di ToList.

Generalmente non dovresti usare ToList () a meno che il lavoro che stai facendo non possa essere svolto senza convertire la raccolta in Elenco. Ad esempio, se desideri semplicemente scorrere la raccolta, non devi eseguire ToList

Se si eseguono query su un'origine dati, ad esempio un database che utilizza LINQ to SQL, il costo di ToList è molto più elevato perché quando si utilizza ToList con LINQ to SQL invece di eseguire l'esecuzione ritardata, ad esempio caricare gli elementi quando necessario (il che può essere utile in molti scenari) carica istantaneamente elementi dal database in memoria


Haris: cosa non sono sicuro della fonte originale cosa accadrà alla fonte originale dopo aver chiamato ToList ()
TalentTuner

@Saurabh GC lo pulirà
pswg

@Saurabh non accadrà nulla alla fonte originale. Gli elementi della fonte originale saranno indicati dall'elenco appena creato
Haris Hasan,

"se vuoi semplicemente scorrere la raccolta non devi eseguire ToList" - quindi come dovresti ripetere?
SharpC,

4

Sarà altrettanto (in) efficiente quanto:

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

Se disassembli il codice sorgente del costruttore che accetta un IEnumerable<T>, vedrai che farà alcune cose:

  • Chiama collection.Count, quindi se collectionè un IEnumerable<T>, forzerà l'esecuzione. Se collectionè un array, un elenco, ecc. Dovrebbe essere O(1).

  • Se collectionimplementa ICollection<T>, salverà gli elementi in un array interno usando il ICollection<T>.CopyTometodo Esso dovrebbe essere O(n), essendo nla lunghezza della collezione.

  • Se collectionnon implementato ICollection<T>, eseguirà l'iterazione degli elementi della raccolta e li aggiungerà a un elenco interno.

Quindi, sì, consumerà più memoria, dal momento che deve creare un nuovo elenco e, nel peggiore dei casi, lo saràO(n) , poiché eseguirà l'iterazione collectionper creare una copia di ciascun elemento.


3
chiudi, 0(n)dov'è nla somma totale dei byte occupati dalle stringhe nella raccolta originale, non il conteggio degli elementi (beh per essere più esatti n = byte / dimensione delle parole)
user1416420

@ user1416420 Potrei sbagliarmi, ma perché? Che cosa succede se si tratta di una collezione di qualche altro tipo (ad es. bool, intEcc)? Non è necessario creare una copia di ogni stringa nella raccolta. Li aggiungi semplicemente al nuovo elenco.
Oscar Mederos,

ancora non importa la nuova allocazione di memoria e la copia di byte è ciò che sta uccidendo questo metodo. Un bool occuperà anche 4 byte in .NET. In realtà ogni riferimento di un oggetto in .NET è lungo almeno 8 byte, quindi è piuttosto lento. i primi 4 byte indicano la tabella dei tipi e i secondi 4 byte indicano il valore o la posizione della memoria in cui trovare il valore
user1416420

3

Considerando le prestazioni del recupero dell'elenco dei file, ToList()è trascurabile. Ma non proprio per altri scenari. Dipende molto da dove lo stai usando.

  • Quando si chiama un array, un elenco o un'altra raccolta, si crea una copia della raccolta come List<T>. Le prestazioni qui dipendono dalle dimensioni dell'elenco. Dovresti farlo quando è veramente necessario.

    Nel tuo esempio, lo chiami su un array. Iterate sull'array e aggiunge gli elementi uno alla volta a un elenco appena creato. Quindi l'impatto sulle prestazioni dipende dal numero di file.

  • Quando si chiama un IEnumerable<T>, si materializza il IEnumerable<T>(di solito una query).


2

ToList Creerà un nuovo elenco e copierà gli elementi dall'origine originale all'elenco appena creato, quindi l'unica cosa è copiare gli elementi dall'origine originale e dipende dalle dimensioni dell'origine

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.