È meglio chiamare ToList () o ToArray () nelle query LINQ?


519

Mi capita spesso di imbattermi nel caso in cui voglio valutare una query proprio dove la dichiaro. Questo di solito è perché ho bisogno di ripeterlo più volte ed è costoso da calcolare. Per esempio:

string raw = "...";
var lines = (from l in raw.Split('\n')
             let ll = l.Trim()
             where !string.IsNullOrEmpty(ll)
             select ll).ToList();

Funziona benissimo. Ma se non ho intenzione di modificare il risultato, allora potrei anche chiamare ToArray()invece di ToList().

Mi chiedo tuttavia se ToArray()è implementato dalla prima chiamata ToList()ed è quindi meno efficiente in termini di memoria rispetto alla sola chiamata ToList().

Sono pazzo? Devo solo chiamare ToArray()- sicuro e protetto nella consapevolezza che la memoria non verrà allocata due volte?


10
Se mai vuoi scoprire cosa succede dietro le tende in .NET, consiglio vivamente .NET Reflector
David Hedlund,

32
@DavidHedlund Raccomando il codice sorgente .net .
Gqqnbig,

1
Non sono d'accordo sul fatto che stackoverflow.com/questions/6750447/c-toarray-performance sia un duplicato di questa domanda anche se esiste una relazione importante. Sia l'uso della memoria (questa domanda) che le prestazioni (altra domanda) sono considerazioni interessanti e non banali. Possono essere descritti separatamente, ma entrambi dovrebbero prendere in considerazione la decisione di scegliere l'uno rispetto all'altro. Non posso raccomandare nessuna delle risposte a questa o all'altra domanda in modo esauriente. Esistono diverse risposte che, se prese insieme, forniscono una discussione piuttosto completa su come scegliere l'una rispetto all'altra.
Steve

1
@Gqqnbig - il commento più utile di sempre! Grazie :-)
Mark Cooper,

Risposte:


366

A meno che non sia semplicemente necessario un array per soddisfare altri vincoli che è necessario utilizzare ToList. Nella maggior parte degli scenari ToArrayallocerà più memoria di ToList.

Entrambi usano array per l'archiviazione, ma ToListhanno un vincolo più flessibile. È necessario che l'array sia grande almeno quanto il numero di elementi nella raccolta. Se l'array è più grande, questo non è un problema. Tuttavia ToArray, l'array deve essere dimensionato esattamente in base al numero di elementi.

Per soddisfare questo vincolo ToArrayspesso fa ancora un'allocazione di ToList. Una volta che ha un array abbastanza grande, alloca un array delle dimensioni esatte e copia gli elementi in quell'array. L'unica volta che può evitarlo è quando l'algoritmo di crescita per l'array coincide con il numero di elementi che devono essere memorizzati (sicuramente in minoranza).

MODIFICARE

Un paio di persone mi hanno chiesto delle conseguenze di avere la memoria inutilizzata in più nel List<T>valore.

Questa è una preoccupazione valida. Se la collezione creata ha una vita lunga, non viene mai modificata dopo essere stata creata e ha un'alta probabilità di atterrare nell'heap Gen2, allora potresti stare meglio prendendo l'allocazione aggiuntiva diToArray fronte.

In generale, però, trovo che questo sia il caso più raro. È molto più comune vederne molteToArray chiamate che vengono immediatamente passate ad altri usi di memoria di breve durata, nel qual caso ToListè dimostrabilmente migliore.

La chiave qui è di profilare, profilare e quindi profilare ancora un po '.


14
D'altra parte, la memoria aggiuntiva allocata per il lavoro di creazione dell'array non sarebbe ammissibile per la garbage collection, mentre rimarrebbe l'overhead aggiuntivo per l'elenco? Dico che è più semplice. Se devi aggiungere o rimuovere elementi, c'è uno strumento per questo. In caso contrario, esiste uno strumento diverso per quello. Usa quello che ha senso. Se più tardi, scopri un problema con la memoria e le prestazioni, e questo è tutto , modificalo.
Anthony Pegram,

1
@AnthonyPegram sì, è una considerazione valida da fare. Se il valore viene utilizzato nell'archiviazione a lungo termine, non verrà modificato e potenzialmente lo trasformerà in Gen 2, quindi è meglio pagare ora l'allocazione aggiuntiva rispetto all'inquinamento dell'heap Gen 2. IME anche se lo vedo raramente. È molto più comune vedere ToArray passare immediatamente a un'altra query LINQ di breve durata.
JaredPar,

2
@AnthonyPegram ho aggiornato la mia risposta per includere questo lato della discussione
JaredPar

8
@JaredPar Non capisco come ToArraypossa allocare più memoria se ha bisogno delle dimensioni esatte delle posizioni dove ToList<>ovviamente ha le sue posizioni di riserva automatiche. (aumento automatico)
Royi Namir,

5
@RoyiNamir perché ToArray esegue prima le allocazioni in stile ToList con overhead, quindi esegue un'allocazione aggiuntiva di dimensioni esatte.
Timbo,

169

La differenza di prestazioni sarà insignificante, poiché List<T>è implementata come un array di dimensioni dinamiche. Chiamata ToArray()(che utilizza una Buffer<T>classe interna per espandere l'array) o ToList()(che chiama ilList<T>(IEnumerable<T>) costruttore) finirà per essere una questione di metterli in un array e far crescere l'array finché non si adatta a tutti.

Se desideri una conferma concreta di questo fatto, controlla l'implementazione dei metodi in questione in Reflector: vedrai che si riducono a un codice quasi identico.


2
Un fatto interessante che mi sono imbattuto è che per le query correlate causate dall'utilizzo di un gruppo definito tramite un join di gruppo nella tua proiezione, Linq to SQL aggiunge un'altra sottoquery per recuperare il conteggio per quel gruppo. Suppongo che ciò significhi in questi casi che la dimensione della raccolta sarà nota prima che gli elementi vengano recuperati e quindi si possa creare direttamente un array di dimensioni esatte che risparmierebbe sulle risorse di elaborazione e memoria mentre materializza i risultati.
jpierson,

133
Se il conteggio è noto in anticipo, le prestazioni sono identiche. Tuttavia, se il conteggio non è noto in anticipo, l'unica differenza tra ToArray()e ToList()è che il primo deve tagliare l'eccesso, il che comporta la copia dell'intero array, mentre il secondo non taglia l'eccesso, ma utilizza una media di 25 % di memoria in più. Ciò avrà implicazioni solo se il tipo di dati è di grandi dimensioni struct. Solo spunti di riflessione.
Scott Rippey,

9
@EldritchConundrum Il 25% deriva da questa logica: se il numero di elementi è sconosciuto, la chiamata ToListo ToArrayinizierà con la creazione di un piccolo buffer. Quando quel buffer viene riempito, raddoppia la capacità del buffer e continua. Poiché la capacità è sempre raddoppiata, il buffer non utilizzato sarà sempre compreso tra 0% e 50%.
Scott Rippey,

2
@ScottRippey Ho appena cercato l'origine del nuovo elenco dall'origine IEnumerable e verifica se IEnumerable è un ICollection e, in caso affermativo, inizia allocando un array con le dimensioni esatte necessarie dalla proprietà Count, quindi questo sarebbe il caso in cui ToList () sarebbe sicuramente più veloce. Una risposta completa potrebbe includere questo fatto, anche se non penso che sia il caso più comune.
AndyClaw,

3
@AndyClaw Entrambi Liste Bufferverificheranno ICollection, nel qual caso le prestazioni saranno identiche.
Scott Rippey,

54

(sette anni dopo ...)

Un paio di altre (buone) risposte si sono concentrate sulle microscopiche differenze di prestazioni che si verificheranno.

Questo post è solo un supplemento per menzionare la differenza semantica che esiste tra il IEnumerator<T>prodotto da un array ( T[]) rispetto a quello restituito da unList<T> .

Meglio illustrato con l'esempio:

IList<int> source = Enumerable.Range(1, 10).ToArray();  // try changing to .ToList()

foreach (var x in source)
{
  if (x == 5)
    source[8] *= 100;
  Console.WriteLine(x);
}

Il codice precedente verrà eseguito senza eccezioni e produce l'output:

1
2
3
4
5
6
7
8
900
10

Ciò dimostra che il IEnumarator<int>restituito da an int[]non tiene traccia del fatto che l'array sia stato modificato dalla creazione dell'enumeratore.

Si noti che ho dichiarato la variabile locale sourcecome un IList<int>. In questo modo mi assicuro che il compilatore C # non ottimizzi l' foreachistruzione in qualcosa che equivale a un for (var idx = 0; idx < source.Length; idx++) { /* ... */ }ciclo. Questo è qualcosa che il compilatore C # potrebbe fare se var source = ...;invece lo utilizzassi . Nella mia attuale versione di .NET framework l'enumeratore utilizzato qui è un tipo di riferimento non pubblico, System.SZArrayHelper+SZGenericArrayEnumerator`1[System.Int32]ma ovviamente si tratta di un dettaglio di implementazione.

Ora, se cambio .ToArray()in .ToList(), ottengo solo:

1
2
3
4
5

seguito da System.InvalidOperationExceptionun'esplosione che dice:

La raccolta è stata modificata; l'operazione di enumerazione potrebbe non essere eseguita.

L'enumeratore sottostante in questo caso è il tipo di valore mutabile pubblico System.Collections.Generic.List`1+Enumerator[System.Int32](racchiuso in una IEnumerator<int>scatola in questo caso perché io uso IList<int>).

In conclusione, l'enumeratore prodotto da unList<T>tiene traccia del fatto che l'elenco cambi durante l'enumerazione, mentre l'enumeratore prodotto daT[]no. Quindi considera questa differenza quando scegli tra.ToList()e.ToArray().

Le persone spesso ne aggiungono uno in più .ToArray() o .ToList()per eludere una raccolta che tiene traccia del fatto che sia stata modificata durante la vita di un enumeratore.

(Se qualcuno vuole sapere come il List<>registra se la raccolta è stata modificata, v'è un campo privato _versionin questa classe che viene cambiata ogni volta che il List<>è aggiornato.)


28

Concordo con @mquander sul fatto che la differenza di prestazioni dovrebbe essere insignificante. Tuttavia, volevo fare un benchmark per essere sicuro, così ho fatto - ed è, insignificante.

Testing with List<T> source:
ToArray time: 1934 ms (0.01934 ms/call), memory used: 4021 bytes/array
ToList  time: 1902 ms (0.01902 ms/call), memory used: 4045 bytes/List

Testing with array source:
ToArray time: 1957 ms (0.01957 ms/call), memory used: 4021 bytes/array
ToList  time: 2022 ms (0.02022 ms/call), memory used: 4045 bytes/List

Ogni matrice / elenco di origine aveva 1000 elementi. Quindi puoi vedere che le differenze di tempo e memoria sono trascurabili.

La mia conclusione: potresti anche usare ToList () , poiché a List<T>fornisce più funzionalità di un array, a meno che alcuni byte di memoria non contino davvero per te.


1
Mi chiedo se questo risultato sarebbe diverso se utilizzassi un structtipo o una classe di grandi dimensioni anziché primitivi.
Scott Rippey,

12
Elenco <T> .Lista ???? Che senso? Faresti meglio a provare a fornire un IEnumerable, che non implementa l'interfaccia ICollection.
Grigory,

8
Volevo assicurarmi di misurare solo il tempo della chiamata ToListo ToArraye non l'enumerazione di nessuna IEnumerable. Elenco <T> .ToList () crea ancora un nuovo elenco <T> - non semplicemente "restituisce questo".
EMP,

23
-1 I comportamenti ToArray()e ToList()differiscono troppo quando vengono forniti con un ICollection<T>parametro: eseguono solo una singola allocazione e una singola operazione di copia. Entrambi List<T>e Arrayimplementano ICollection<T>, quindi i tuoi benchmark non sono affatto validi.
Mohammad Dehghan,

1
Per chiunque sia interessato, ho pubblicato il mio benchmark come risposta separata . Utilizza .Select(i => i)per evitare il ICollection<T>problema di implementazione e include un gruppo di controllo per vedere quanto tempo è impiegato per IEnumerable<>la prima volta sull'iterazione dell'origine .
StriplingWarrior,

19

ToList()è di solito preferito se lo usi su IEnumerable<T>(da ORM, per esempio). Se la lunghezza della sequenza non è nota all'inizio, ToArray()crea una raccolta a lunghezza dinamica come Elenco e quindi la converte in matrice, il che richiede più tempo.


26
Ho deciso che la leggibilità supera le prestazioni in questo caso. Ora uso ToList solo quando mi aspetto di continuare ad aggiungere elementi. In tutti gli altri casi (nella maggior parte dei casi), utilizzo ToArray. Ma grazie per l'input!
Frank Krueger,

5
Guardando in ILSpy, Enumerable.ToArray()chiama new Buffer<TSource>(source).ToArray(). Nel costruttore Buffer se l'origine implementa ICollection, chiama source.CopyTo (items, 0) e quindi .ToArray () restituisce direttamente l'array degli elementi interni. Quindi non vi è alcuna conversione che richiede tempo extra in quel caso. Se l'origine non implementa ICollection, ToArray genererà una copia dell'array al fine di tagliare le posizioni extra non utilizzate dalla fine dell'array come descritto dal commento di Scott Rippey sopra.
BrandonAGr

19

La memoria verrà sempre allocata due volte - o qualcosa di simile. Poiché non è possibile ridimensionare un array, entrambi i metodi utilizzeranno una sorta di meccanismo per raccogliere i dati in una raccolta in crescita. (Bene, l'elenco è una raccolta in crescita in sé.)

L'elenco utilizza un array come memoria interna e raddoppia la capacità quando necessario. Ciò significa che in media 2/3 degli articoli sono stati riallocati almeno una volta, metà di quelli riallocati almeno due volte, metà di almeno tre volte e così via. Ciò significa che ogni articolo è stato riallocato in media 1,3 volte, il che non è un grande sovraccarico.

Ricordare inoltre che se si raccolgono stringhe, la raccolta stessa contiene solo i riferimenti alle stringhe, le stringhe stesse non vengono riallocate.


Questa può essere una cosa ignorante da chiedere, ma la logica 2/3, 1/3, 1/6 che tracci non presuppone che l'array dell'Elenco possa essere esteso in posizione? Cioè, c'è spazio libero alla fine dell'array in modo che l'allocazione esistente non debba essere spostata?

@JonofAllTrades: No, l'array non viene mai esteso sul posto, la gestione della memoria in .NET semplicemente non lo fa. Se fosse esteso sul posto, non sarebbe necessario riallocare gli articoli.
Guffa,

Ah, vedo: gli elementi che non sono stati riallocati non dovevano farlo perché erano nell'allocazione finale. Tutti gli elementi allocati nelle allocazioni precedenti vengono spostati, ma a causa degli aumenti logaritmici della lunghezza dell'array questa è una frazione calcolabile. Grazie per il chiarimento!

19

È il 2020 fuori e tutti usano .NET Core 3.1, quindi ho deciso di eseguire alcuni benchmark con Benchmark.NET.

TL; DR: ToArray () è migliore dal punto di vista delle prestazioni e fa un lavoro migliore trasmettendo intenti se non hai intenzione di mutare la raccolta.


    [MemoryDiagnoser]
    public class Benchmarks
    {
        [Params(0, 1, 6, 10, 39, 100, 666, 1000, 1337, 10000)]
        public int Count { get; set; }

        public IEnumerable<int> Items => Enumerable.Range(0, Count);

        [Benchmark(Description = "ToArray()", Baseline = true)]
        public int[] ToArray() => Items.ToArray();

        [Benchmark(Description = "ToList()")]
        public List<int> ToList() => Items.ToList();

        public static void Main() => BenchmarkRunner.Run<Benchmarks>();
    }

I risultati sono:


    BenchmarkDotNet=v0.12.0, OS=Windows 10.0.14393.3443 (1607/AnniversaryUpdate/Redstone1)
    Intel Core i5-4460 CPU 3.20GHz (Haswell), 1 CPU, 4 logical and 4 physical cores
    Frequency=3124994 Hz, Resolution=320.0006 ns, Timer=TSC
    .NET Core SDK=3.1.100
      [Host]     : .NET Core 3.1.0 (CoreCLR 4.700.19.56402, CoreFX 4.700.19.56404), X64 RyuJIT
      DefaultJob : .NET Core 3.1.0 (CoreCLR 4.700.19.56402, CoreFX 4.700.19.56404), X64 RyuJIT


    |    Method | Count |          Mean |       Error |      StdDev |        Median | Ratio | RatioSD |   Gen 0 | Gen 1 | Gen 2 | Allocated |
    |---------- |------ |--------------:|------------:|------------:|--------------:|------:|--------:|--------:|------:|------:|----------:|
    | ToArray() |     0 |      7.357 ns |   0.2096 ns |   0.1960 ns |      7.323 ns |  1.00 |    0.00 |       - |     - |     - |         - |
    |  ToList() |     0 |     13.174 ns |   0.2094 ns |   0.1958 ns |     13.084 ns |  1.79 |    0.05 |  0.0102 |     - |     - |      32 B |
    |           |       |               |             |             |               |       |         |         |       |       |           |
    | ToArray() |     1 |     23.917 ns |   0.4999 ns |   0.4676 ns |     23.954 ns |  1.00 |    0.00 |  0.0229 |     - |     - |      72 B |
    |  ToList() |     1 |     33.867 ns |   0.7350 ns |   0.6876 ns |     34.013 ns |  1.42 |    0.04 |  0.0331 |     - |     - |     104 B |
    |           |       |               |             |             |               |       |         |         |       |       |           |
    | ToArray() |     6 |     28.242 ns |   0.5071 ns |   0.4234 ns |     28.196 ns |  1.00 |    0.00 |  0.0280 |     - |     - |      88 B |
    |  ToList() |     6 |     43.516 ns |   0.9448 ns |   1.1949 ns |     42.896 ns |  1.56 |    0.06 |  0.0382 |     - |     - |     120 B |
    |           |       |               |             |             |               |       |         |         |       |       |           |
    | ToArray() |    10 |     31.636 ns |   0.5408 ns |   0.4516 ns |     31.657 ns |  1.00 |    0.00 |  0.0331 |     - |     - |     104 B |
    |  ToList() |    10 |     53.870 ns |   1.2988 ns |   2.2403 ns |     53.415 ns |  1.77 |    0.07 |  0.0433 |     - |     - |     136 B |
    |           |       |               |             |             |               |       |         |         |       |       |           |
    | ToArray() |    39 |     58.896 ns |   0.9441 ns |   0.8369 ns |     58.548 ns |  1.00 |    0.00 |  0.0713 |     - |     - |     224 B |
    |  ToList() |    39 |    138.054 ns |   2.8185 ns |   3.2458 ns |    138.937 ns |  2.35 |    0.08 |  0.0815 |     - |     - |     256 B |
    |           |       |               |             |             |               |       |         |         |       |       |           |
    | ToArray() |   100 |    119.167 ns |   1.6195 ns |   1.4357 ns |    119.120 ns |  1.00 |    0.00 |  0.1478 |     - |     - |     464 B |
    |  ToList() |   100 |    274.053 ns |   5.1073 ns |   4.7774 ns |    272.242 ns |  2.30 |    0.06 |  0.1578 |     - |     - |     496 B |
    |           |       |               |             |             |               |       |         |         |       |       |           |
    | ToArray() |   666 |    569.920 ns |  11.4496 ns |  11.2450 ns |    571.647 ns |  1.00 |    0.00 |  0.8688 |     - |     - |    2728 B |
    |  ToList() |   666 |  1,621.752 ns |  17.1176 ns |  16.0118 ns |  1,623.566 ns |  2.85 |    0.05 |  0.8793 |     - |     - |    2760 B |
    |           |       |               |             |             |               |       |         |         |       |       |           |
    | ToArray() |  1000 |    796.705 ns |  16.7091 ns |  19.8910 ns |    796.610 ns |  1.00 |    0.00 |  1.2951 |     - |     - |    4064 B |
    |  ToList() |  1000 |  2,453.110 ns |  48.1121 ns |  65.8563 ns |  2,460.190 ns |  3.09 |    0.10 |  1.3046 |     - |     - |    4096 B |
    |           |       |               |             |             |               |       |         |         |       |       |           |
    | ToArray() |  1337 |  1,057.983 ns |  20.9810 ns |  41.4145 ns |  1,041.028 ns |  1.00 |    0.00 |  1.7223 |     - |     - |    5416 B |
    |  ToList() |  1337 |  3,217.550 ns |  62.3777 ns |  61.2633 ns |  3,203.928 ns |  2.98 |    0.13 |  1.7357 |     - |     - |    5448 B |
    |           |       |               |             |             |               |       |         |         |       |       |           |
    | ToArray() | 10000 |  7,309.844 ns | 160.0343 ns | 141.8662 ns |  7,279.387 ns |  1.00 |    0.00 | 12.6572 |     - |     - |   40064 B |
    |  ToList() | 10000 | 23,858.032 ns | 389.6592 ns | 364.4874 ns | 23,759.001 ns |  3.26 |    0.08 | 12.6343 |     - |     - |   40096 B |

    // * Hints *
    Outliers
      Benchmarks.ToArray(): Default -> 2 outliers were removed (35.20 ns, 35.29 ns)
      Benchmarks.ToArray(): Default -> 2 outliers were removed (38.51 ns, 38.88 ns)
      Benchmarks.ToList(): Default  -> 1 outlier  was  removed (64.69 ns)
      Benchmarks.ToArray(): Default -> 1 outlier  was  removed (67.02 ns)
      Benchmarks.ToArray(): Default -> 1 outlier  was  removed (130.08 ns)
      Benchmarks.ToArray(): Default -> 1 outlier  was  detected (541.82 ns)
      Benchmarks.ToArray(): Default -> 1 outlier  was  removed (7.82 us)

    // * Legends *
      Count     : Value of the 'Count' parameter
      Mean      : Arithmetic mean of all measurements
      Error     : Half of 99.9% confidence interval
      StdDev    : Standard deviation of all measurements
      Median    : Value separating the higher half of all measurements (50th percentile)
      Ratio     : Mean of the ratio distribution ([Current]/[Baseline])
      RatioSD   : Standard deviation of the ratio distribution ([Current]/[Baseline])
      Gen 0     : GC Generation 0 collects per 1000 operations
      Gen 1     : GC Generation 1 collects per 1000 operations
      Gen 2     : GC Generation 2 collects per 1000 operations
      Allocated : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)
      1 ns      : 1 Nanosecond (0.000000001 sec)

1
Se non hai intenzione di mutare la collezione, penso che l'intento possa essere meglio mostrato con ToImmutableArray()(dal pacchetto System.Collections.Immutable) 😉
Arturo Torres Sánchez

@ ArturoTorresSánchez vero, ma se la raccolta non fosse esposta al di fuori di un metodo, utilizzerei solo un array.
Tyrrrz,

2
Grazie per questo. La risposta scelta è un semplice argomento e presuppone risultati a seguito di tale argomento. Per fare questo scientificamente e come bonus sapere quanta differenza c'è, c'è solo un vero modo per sapere.
Jonas

15

modificare : l'ultima parte di questa risposta non è valida. Tuttavia, il resto è ancora un'informazione utile, quindi la lascerò.

So che questo è un vecchio post, ma dopo aver avuto la stessa domanda e fatto qualche ricerca, ho trovato qualcosa di interessante che potrebbe valere la pena condividere.

Innanzitutto, sono d'accordo con @mquander e la sua risposta. Ha ragione nel dire che per quanto riguarda le prestazioni, i due sono identici.

Tuttavia, sto usando Reflector per dare un'occhiata ai metodi nello System.Linq.Enumerablespazio dei nomi delle estensioni e ho notato un'ottimizzazione molto comune.
Ove possibile, la IEnumerable<T>fonte viene trasmessa IList<T>o ICollection<T>per ottimizzare il metodo. Ad esempio, guardaElementAt(int) .

È interessante notare che Microsoft ha scelto di ottimizzare solo per IList<T>, ma non IList. Sembra che Microsoft preferisca usare l' IList<T>interfaccia.

System.Arrayimplementa solo IList, quindi non trarrà vantaggio da nessuna di queste ottimizzazioni di estensione.
Pertanto, sostengo che la migliore pratica è utilizzare il .ToList()metodo.
Se si utilizza uno dei metodi di estensione o si passa l'elenco a un altro metodo, è possibile che sia ottimizzato per un IList<T>.


16
Ho fatto un test e ho scoperto qualcosa di sorprendente. Un array implementa IList <T>! Utilizzando Reflector per analizzare System.Array rivela solo una catena di ereditarietà di IList, ICollection, IEnumerable ma utilizzando la riflessione di runtime ho scoperto che string [] ha una catena di ereditarietà di IList, ICollection, IEnumerable, IList <string>, ICollection <string >, IEnumerable <string>. Pertanto, non ho una risposta migliore di @mquander!
Scott Rippey,

@ScottRippey Sì. La strana osservazione che hai notato è in realtà parte di un "hack" - e ha anche alcune implicazioni piuttosto strane riguardo a "dimensioni fisse" e proprietà simili (con alcune incongruenze a seconda di come lo lanci). Ci sono alcuni commenti abbastanza grandi che toccano questo argomento all'interno del codice sorgente .net. Ci scusiamo per non aver collegato, ma se ricordo bene è abbastanza facile da trovare (all'interno della classe array). (E c'è anche una grande domanda SO che discute delle incongruenze .... da qualche parte ...> __>)
AnorZaken,

@ScottRippey solo FYI Ho trovato questa risposta, che ha a che fare con il proprio commento: stackoverflow.com/a/4482567/2063755
David Klempfner

14

Ho scoperto che mancano gli altri parametri di riferimento che la gente ha fatto qui, quindi ecco il mio crack. Fammi sapere se trovi qualcosa di sbagliato nella mia metodologia.

/* This is a benchmarking template I use in LINQPad when I want to do a
 * quick performance test. Just give it a couple of actions to test and
 * it will give you a pretty good idea of how long they take compared
 * to one another. It's not perfect: You can expect a 3% error margin
 * under ideal circumstances. But if you're not going to improve
 * performance by more than 3%, you probably don't care anyway.*/
void Main()
{
    // Enter setup code here
    var values = Enumerable.Range(1, 100000)
        .Select(i => i.ToString())
        .ToArray()
        .Select(i => i);
    values.GetType().Dump();
    var actions = new[]
    {
        new TimedAction("ToList", () =>
        {
            values.ToList();
        }),
        new TimedAction("ToArray", () =>
        {
            values.ToArray();
        }),
        new TimedAction("Control", () =>
        {
            foreach (var element in values)
            {
                // do nothing
            }
        }),
        // Add tests as desired
    };
    const int TimesToRun = 1000; // Tweak this as necessary
    TimeActions(TimesToRun, actions);
}


#region timer helper methods
// Define other methods and classes here
public void TimeActions(int iterations, params TimedAction[] actions)
{
    Stopwatch s = new Stopwatch();
    int length = actions.Length;
    var results = new ActionResult[actions.Length];
    // Perform the actions in their initial order.
    for (int i = 0; i < length; i++)
    {
        var action = actions[i];
        var result = results[i] = new ActionResult { Message = action.Message };
        // Do a dry run to get things ramped up/cached
        result.DryRun1 = s.Time(action.Action, 10);
        result.FullRun1 = s.Time(action.Action, iterations);
    }
    // Perform the actions in reverse order.
    for (int i = length - 1; i >= 0; i--)
    {
        var action = actions[i];
        var result = results[i];
        // Do a dry run to get things ramped up/cached
        result.DryRun2 = s.Time(action.Action, 10);
        result.FullRun2 = s.Time(action.Action, iterations);
    }
    results.Dump();
}

public class ActionResult
{
    public string Message { get; set; }
    public double DryRun1 { get; set; }
    public double DryRun2 { get; set; }
    public double FullRun1 { get; set; }
    public double FullRun2 { get; set; }
}

public class TimedAction
{
    public TimedAction(string message, Action action)
    {
        Message = message;
        Action = action;
    }
    public string Message { get; private set; }
    public Action Action { get; private set; }
}

public static class StopwatchExtensions
{
    public static double Time(this Stopwatch sw, Action action, int iterations)
    {
        sw.Restart();
        for (int i = 0; i < iterations; i++)
        {
            action();
        }
        sw.Stop();

        return sw.Elapsed.TotalMilliseconds;
    }
}
#endregion

Puoi scaricare lo script LINQPad qui .

risultati: ToArray vs ToList performance

Modificando il codice sopra, scoprirai che:

  1. La differenza è meno significativa quando si ha a che fare con array più piccoli . Più iterazioni, ma array più piccoli
  2. La differenza è meno rilevante quando si tratta di ints piuttosto che strings.
  3. L'uso di grandi structs invece di strings richiede molto più tempo in generale, ma in realtà non cambia molto il rapporto.

Ciò concorda con le conclusioni delle risposte più votate:

  1. È improbabile che tu noti una differenza di prestazioni a meno che il tuo codice non produca frequentemente molti grandi elenchi di dati. (C'era solo una differenza di 200ms durante la creazione di 1000 liste di stringhe da 100K ciascuna).
  2. ToList() funziona costantemente più velocemente e sarebbe una scelta migliore se non hai intenzione di aggrapparti ai risultati per molto tempo.

Aggiornare

@JonHanna ha sottolineato che a seconda dell'implementazione Selectè possibile per una ToList()o ToArray()implementazione prevedere in anticipo le dimensioni della raccolta risultante. La sostituzione .Select(i => i)nel codice sopra con Where(i => true) risultati molto simili al momento, ed è più probabile che lo faccia indipendentemente dall'implementazione di .NET.

Benchmark utilizzando Where anziché Select


In .NET Core entrambi i casi dovrebbero essere migliori qui che su netfx, perché capirà che la dimensione sarà 100000e la userà per ottimizzare entrambi ToList()e ToArray(), ToArray()essendo leggermente più leggera perché non ha bisogno dell'operazione di restringimento di cui avrebbe bisogno altrimenti, che è l'unico posto ToList()ha il vantaggio. L'esempio nella domanda si perderebbe ancora, perché i Wheremezzi che tale previsione delle dimensioni non può essere fatta.
Jon Hanna,

@JonHanna: grazie per il rapido feedback. Non sapevo che .NET Core stesse facendo quell'ottimizzazione. Questo è figo. Nel mio codice, .Select(i => i)potrebbe essere sostituito con .Where(i => true)per correggere.
StriplingWarrior,

Sì, ciò impedirebbe l'ottimizzazione che la riguarda su corefx. Potrebbe essere interessante avere sia una dimensione che sia una potenza di due (che dovrebbe dare ToArray()un vantaggio) e una che non è, come sopra, e confrontare i risultati.
Jon Hanna,

@JonHanna: È interessante notare ToArray() che perde ancora nello scenario migliore. Con Math.Pow(2, 15)elementi, è (ToList: 700ms, ToArray: 900ms). L'aggiunta di un altro elemento lo blocca in (Elenco di elementi: 925, Array: 1350). Mi chiedo se ToArraysta ancora copiando l'array anche quando ha già le dimensioni perfette? Probabilmente hanno pensato che fosse un evento abbastanza raro che non valesse il condizionale in più.
StriplingWarrior,

Non è stato copiato su una corrispondenza di dimensioni esatte, anche prima di iniziare a ottimizzarlo in corefx, quindi è il caso in cui ottiene il maggior numero di interruzioni.
Jon Hanna,

12

Dovresti basare la tua decisione di scegliere ToListo ToArrayidealmente la scelta del design. Se si desidera una raccolta che può essere iterata e accessibile solo per indice, selezionare ToArray. Se desideri ulteriori funzionalità di aggiunta e rimozione dalla raccolta in un secondo momento senza troppe seccature, procedi nel seguente modoToList (non proprio che non puoi aggiungerlo a un array, ma di solito non è lo strumento giusto).

Se le prestazioni sono importanti, dovresti anche considerare ciò su cui sarebbe più veloce operare. Realisticamente, non chiamerai ToListo ToArrayun milione di volte, ma potresti lavorare sulla raccolta ottenuta un milione di volte. Sotto questo aspetto []è meglio, poiché List<>è []con un certo sovraccarico. Vedi questo thread per un confronto sull'efficienza: Quale è più efficiente: Elenco <int> o int []

Nei miei test qualche tempo fa, l'avevo trovato ToArraypiù velocemente. E non sono sicuro di quanto siano stati distorti i test. La differenza di prestazioni è così insignificante, il che può essere notato solo se si eseguono queste query in un ciclo milioni di volte.


2
Sì - se il compilatore sa che stai eseguendo l'iterazione su un array (anziché su un IEnumerable <>), può ottimizzare l'iterazione in modo significativo.
RobSiklos,

12

Una risposta molto tardi, ma penso che sarà utile per i googler.

Entrambi fanno schifo quando hanno creato usando Linq. Entrambi implementano lo stesso codice per ridimensionare il buffer, se necessario . ToArrayutilizza internamente una classe per la conversioneIEnumerable<> in array, allocando un array di 4 elementi. Se ciò non bastasse, raddoppia la dimensione creando un nuovo array che raddoppia le dimensioni dell'attuale e copiando l'array corrente su di esso. Alla fine assegna un nuovo array di conteggi dei tuoi articoli. Se la tua query restituisce 129 elementi, ToArray eseguirà 6 allocazioni e operazioni di copia della memoria per creare un array di 256 elementi e quindi un altro array di 129 da restituire. così tanto per l'efficienza della memoria.

ToList fa la stessa cosa, ma salta l'ultima allocazione poiché è possibile aggiungere elementi in futuro. L'elenco non importa se viene creato da una query linq o creato manualmente.

per la creazione L'elenco è migliore con la memoria, ma peggio con la CPU poiché l'elenco è una soluzione generica ogni azione richiede controlli di intervallo aggiuntivi ai controlli di intervallo interno della rete per gli array.

Quindi, se ripeterai il tuo set di risultati troppe volte, allora gli array sono buoni poiché significa meno controlli di intervallo rispetto agli elenchi e i compilatori generalmente ottimizzano gli array per l'accesso sequenziale.

L'allocazione di inizializzazione dell'elenco può essere migliore se si specifica il parametro di capacità al momento della creazione. In questo caso, allocerà l'array solo una volta, supponendo che tu conosca la dimensione del risultato. ToListof linq non specifica un sovraccarico per fornirlo, quindi dobbiamo creare il nostro metodo di estensione che crea un elenco con una determinata capacità e quindi lo utilizza List<>.AddRange.

Per finire questa risposta devo scrivere le seguenti frasi

  1. Alla fine, è possibile utilizzare un ToArray o ToList, le prestazioni non saranno così diverse (vedere la risposta di @EMP).
  2. Stai usando C #. Se hai bisogno di prestazioni, non preoccuparti di scrivere codice ad alte prestazioni, ma preoccupati di non scrivere codice prestazioni scadenti.
  3. Puntare sempre a x64 per codice ad alte prestazioni. AFAIK, x64 JIT è basato sul compilatore C ++ e fa cose divertenti come l'ottimizzazione della ricorsione della coda.
  4. Con 4.5 puoi anche goderti l'ottimizzazione guidata dal profilo e il JIT multi core.
  5. Alla fine, è possibile utilizzare il modello asincrono / wait per elaborarlo più rapidamente.

Entrambi fanno schifo? Hai un'idea alternativa che non richiede allocazione di memoria ridondante?
nawfal,

Nel contesto della domanda, sì, entrambi fanno schifo ma a causa di allocazioni ridondanti e nient'altro. Per ridurre l'allocazione ridondante, è possibile utilizzare elenchi collegati a spese della memoria e della velocità di iterazione. Alla fine, questo è ciò che facciamo, facciamo degli scambi. Un'altra idea è se creare un elenco con una capacità di 200 (ad esempio) e quindi caricare gli articoli. Ciò ridurrà anche la ridondanza, ma gli array sono sempre più veloci, quindi questo è un altro compromesso.
Erdogan Kurtur,

Creare un elenco di 200 ? Ciò potrebbe evitare il ridimensionamento, ma stavo parlando della memoria ridondante utilizzata. Non puoi evitarlo perché non ci sono conoscenze preliminari su quale potrebbe essere la dimensione. Puoi già specificare la capacità nel costruttore di a List<T>, ma quando non lo fai o quando non puoi, non puoi farne a meno.
nawfal,

2
gli unici dati ridondanti in memoria sono i contenuti dell'array che è un elenco di puntatori (in questo caso). un milione di puntatori a 64 bit occupa fino a 8 MB di memoria, il che è nulla rispetto a un milione di oggetti a cui puntano. 200 è solo un numero e ha la possibilità di ridurre il numero di chiamate di ridimensionamento per un massimo di 5 volte. e sì, non possiamo farci niente. non abbiamo opzioni migliori. Non ho una soluzione migliore, ma ciò non significa che non mi sia permesso di dire dove si trova il problema.
Erdogan Kurtur,

1
hmm alla fine è dove si disegna la linea. Mi piace l'implementazione attuale. Il tono della tua risposta mi ha fatto pensare che si trattasse di una critica piuttosto che di un problema :)
nawfal

7

Questa è una vecchia domanda - ma a beneficio degli utenti che si imbattono in esso, c'è anche e un'alternativa di 'Memoizing' l'Enumerable - che ha l'effetto di memorizzare nella cache e fermare l'enumerazione multipla di un'istruzione Linq, che è ciò che ToArray () e ToList () sono usati per molto, anche se gli attributi di raccolta della lista o dell'array non sono mai usati.

Memoize è disponibile nella libreria RX / System.Interactive ed è spiegato qui: Altro LINQ con System.Interactive

(Dal blog di Bart De'Smet che è una lettura altamente raccomandata se lavori molto con Linq to Objects)


4

Un'opzione è aggiungere il proprio metodo di estensione che restituisce un valore di sola lettura ICollection<T> . Questo può essere migliore dell'uso ToListo ToArrayquando non si desidera utilizzare le proprietà di indicizzazione di un array / elenco o aggiungere / rimuovere da un elenco.

public static class EnumerableExtension
{
    /// <summary>
    /// Causes immediate evaluation of the linq but only if required.
    /// As it returns a readonly ICollection, is better than using ToList or ToArray
    /// when you do not want to use the indexing properties of an IList, or add to the collection.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="enumerable"></param>
    /// <returns>Readonly collection</returns>
    public static ICollection<T> Evaluate<T>(this IEnumerable<T> enumerable)
    {
        //if it's already a readonly collection, use it
        var collection = enumerable as ICollection<T>;
        if ((collection != null) && collection.IsReadOnly)
        {
            return collection;
        }
        //or make a new collection
        return enumerable.ToList().AsReadOnly();
    }
}

Test unitari:

[TestClass]
public sealed class EvaluateLinqTests
{
    [TestMethod]
    public void EvalTest()
    {
        var list = new List<int> {1, 2, 3};
        var linqResult = list.Select(i => i);
        var linqResultEvaluated = list.Select(i => i).Evaluate();
        list.Clear();
        Assert.AreEqual(0, linqResult.Count());
        //even though we have cleared the underlying list, the evaluated list does not change
        Assert.AreEqual(3, linqResultEvaluated.Count());
    }

    [TestMethod]
    public void DoesNotSaveCreatingListWhenHasListTest()
    {
        var list = new List<int> {1, 2, 3};
        var linqResultEvaluated = list.Evaluate();
        //list is not readonly, so we expect a new list
        Assert.AreNotSame(list, linqResultEvaluated);
    }

    [TestMethod]
    public void SavesCreatingListWhenHasReadonlyListTest()
    {
        var list = new List<int> {1, 2, 3}.AsReadOnly();
        var linqResultEvaluated = list.Evaluate();
        //list is readonly, so we don't expect a new list
        Assert.AreSame(list, linqResultEvaluated);
    }

    [TestMethod]
    public void SavesCreatingListWhenHasArrayTest()
    {
        var list = new[] {1, 2, 3};
        var linqResultEvaluated = list.Evaluate();
        //arrays are readonly (wrt ICollection<T> interface), so we don't expect a new object
        Assert.AreSame(list, linqResultEvaluated);
    }

    [TestMethod]
    [ExpectedException(typeof (NotSupportedException))]
    public void CantAddToResultTest()
    {
        var list = new List<int> {1, 2, 3};
        var linqResultEvaluated = list.Evaluate();
        Assert.AreNotSame(list, linqResultEvaluated);
        linqResultEvaluated.Add(4);
    }

    [TestMethod]
    [ExpectedException(typeof (NotSupportedException))]
    public void CantRemoveFromResultTest()
    {
        var list = new List<int> {1, 2, 3};
        var linqResultEvaluated = list.Evaluate();
        Assert.AreNotSame(list, linqResultEvaluated);
        linqResultEvaluated.Remove(1);
    }
}

Vale la pena notare che il contratto di raccolta di sola lettura stabilisce che l'utente dell'oggetto non può modificarlo, ma il proprietario può comunque farlo se mantiene un riferimento ad esso che offre un'interfaccia mutabile. Per le interfacce che garantiscono che la struttura sottostante non cambierà mai, guarda le collezioni immutabili. Per quanto riguarda perché le collezioni immutabili, di sola lettura o semplici di lettura / scrittura sono migliori o peggiori, è necessario un punto di riferimento per il confronto; non esiste una risposta definitiva (altrimenti non dovremmo scegliere).
TNE

@tne Nota Faccio Tolist prima di AsReadOnly, quindi non ci sono riferimenti al mutabile sottostante.
Weston,

Hai perfettamente ragione, e quello era probabilmente il modo migliore per fare le cose prima che le collezioni immutabili arrivassero al BCL (vedo che la prima beta è uscita un mese dopo la tua risposta).
TNE

Esistono raccolte immutabili per la sicurezza dei thread, in cui i thread possono presumere che non cambierà e, in tal caso, viene creata una nuova versione, invece di correre contro i lettori e cambiarla mentre la usano. In questo modo, nessuno ha mai bisogno di acquisire una serratura.
doug65536,

4

ToListAsync<T>() è preferito.

In Entity Framework 6 entrambi i metodi alla fine chiamano lo stesso metodo interno, ma alla fine ToArrayAsync<T>()chiama list.ToArray(), che è implementato come

T[] array = new T[_size];
Array.Copy(_items, 0, array, 0, _size);
return array;

Quindi ToArrayAsync<T>()ha alcune spese generali, quindi ToListAsync<T>()è preferito.


1
Questa è in realtà la risposta che stavo cercando, come fa EF. Sarei curioso di sapere come va in EF Core.
Shimmy Weitzhandler,

3

Vecchia domanda ma nuovi interrogatori in ogni momento.

Secondo l'origine di System.Linq.Enumerable , ToListbasta restituire a new List(source), mentre ToArrayusare a new Buffer<T>(source).ToArray()per restituire a T[].

Informazioni sull'allocazione di memoria:

Durante l'esecuzione su un IEnumerable<T>unico oggetto, ToArrayallocare memoria più volte di ToList. Ma non devi preoccupartene nella maggior parte dei casi, perché GC eseguirà la raccolta dei rifiuti quando necessario.

Informazioni sul runtime efficiente:

Coloro che mettono in dubbio questa domanda possono eseguire il seguente codice sul proprio computer e otterrai la tua risposta.

class PersonC
{
    public Guid uuid;
    public string name;
    public int age;
    public bool sex;
    public DateTime BirthDay;
    public double weight;
}

struct PersonS
{
    public Guid uuid;
    public string name;
    public int age;
    public bool sex;
    public DateTime BirthDay;
    public double weight;
}

class PersonT<T> : IEnumerable<T>
{
    private List<T> items;
    public PersonT(IEnumerable<T> init)
    {
        items = new List<T>(init);
    }

    public IEnumerator<T> GetEnumerator() => items.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => items.GetEnumerator();
}

private IEnumerable<PersonC> C(int count)
{
    for (var i = 0; i < count; ++i)
    {
        var guid = Guid.NewGuid();
        var guidBytes = guid.ToByteArray(); //16 bytes
        yield return new PersonC
        {
            uuid = guid,
            name = guid.ToString(),
            age = guidBytes[0] ^ guidBytes[7],
            sex = guidBytes[14] % 2 == 0,
            BirthDay = DateTime.Now.AddDays(-guidBytes[11] * 18),
            weight = guidBytes[12] * 100
        };
    }
}

private IEnumerable<PersonS> S(int count)
{
    for (var i = 0; i < count; ++i)
    {
        var guid = Guid.NewGuid();
        var guidBytes = guid.ToByteArray(); //16 bytes
        yield return new PersonS
        {
            uuid = guid,
            name = guid.ToString(),
            age = guidBytes[0] ^ guidBytes[7],
            sex = guidBytes[14] % 2 == 0,
            BirthDay = DateTime.Now.AddDays(-guidBytes[11] * 18),
            weight = guidBytes[12] * 100
        };
    }
}

private void MakeLog(string test, List<long> log) =>
    Console.WriteLine("{0} {1} ms -> [{2}]",
        test,
        log.Average(),
        string.Join(", ", log)
    );

private void Test1(int times, int count)
{
    var test = Enumerable.Range(1, times).ToArray();

    MakeLog("C.ToList", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = C(count).ToList();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("C.ToArray", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = C(count).ToArray();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("S.ToList", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = S(count).ToList();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("S.ToArray", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = S(count).ToArray();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());
}

private void Test2(int times, int count)
{
    var test = Enumerable.Range(1, times).ToArray();

    var dataC1 = new PersonT<PersonC>(C(count));
    var dataS1 = new PersonT<PersonS>(S(count));

    MakeLog("C1.ToList", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataC1.ToList();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("C1.ToArray", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataC1.ToArray();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("S1.ToList", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataS1.ToList();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("S1.ToArray", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataS1.ToArray();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());
}

private void Test3(int times, int count)
{
    var test = Enumerable.Range(1, times).ToArray();

    var dataC2 = (ICollection<PersonC>) new List<PersonC>(C(count));
    var dataS2 = (ICollection<PersonS>) new List<PersonS>(S(count));

    MakeLog("C2.ToList", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataC2.ToList();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("C2.ToArray", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataC2.ToArray();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("S2.ToList", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataS2.ToList();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());

    MakeLog("S2.ToArray", test.Select(o =>
    {
        var sw = new Stopwatch();
        GC.Collect();
        sw.Start();
        var ret = dataS2.ToArray();
        sw.Stop();
        return sw.ElapsedMilliseconds;
    }).ToList());
}

private void TestMain()
{
    const int times = 100;
    const int count = 1_000_000 + 1;
    Test1(times, count);
    Test2(times, count);
    Test3(times, count);
}

Ho ottenuto questi risultati sulla mia macchina:

Gruppo 1:

C.ToList 761.79 ms -> [775, 755, 759, 759, 756, 759, 765, 750, 757, 762, 759, 754, 757, 753, 763, 753, 759, 756, 768, 754, 763, 757, 757, 777, 780, 758, 754, 758, 762, 754, 758, 757, 763, 758, 760, 754, 761, 755, 764, 847, 952, 755, 747, 763, 760, 758, 754, 763, 761, 758, 750, 764, 757, 763, 762, 756, 753, 759, 759, 757, 758, 779, 765, 760, 760, 756, 760, 756, 755, 764, 759, 753, 757, 760, 752, 764, 758, 760, 758, 760, 755, 761, 751, 753, 761, 762, 761, 758, 759, 752, 765, 756, 760, 755, 757, 753, 760, 751, 755, 779]
C.ToArray 782.56 ms -> [783, 774, 771, 771, 773, 774, 775, 775, 772, 770, 771, 774, 771, 1023, 975, 772, 767, 776, 771, 779, 772, 779, 775, 771, 775, 773, 775, 771, 765, 774, 770, 781, 772, 771, 781, 762, 817, 770, 775, 779, 769, 774, 763, 775, 777, 769, 777, 772, 775, 778, 775, 771, 770, 774, 772, 769, 772, 769, 774, 775, 768, 775, 769, 774, 771, 776, 774, 773, 778, 769, 778, 767, 770, 787, 783, 779, 771, 768, 805, 780, 779, 767, 773, 771, 773, 785, 1044, 853, 775, 774, 775, 771, 770, 769, 770, 776, 770, 780, 821, 770]
S.ToList 704.2 ms -> [687, 702, 709, 691, 694, 710, 696, 698, 700, 694, 701, 719, 706, 694, 702, 699, 699, 703, 704, 701, 703, 705, 697, 707, 691, 697, 707, 692, 721, 698, 695, 700, 704, 700, 701, 710, 700, 705, 697, 711, 694, 700, 695, 698, 701, 692, 696, 702, 690, 699, 708, 700, 703, 714, 701, 697, 700, 699, 694, 701, 697, 696, 699, 694, 709, 1068, 690, 706, 699, 699, 695, 708, 695, 704, 704, 700, 695, 704, 695, 696, 702, 700, 710, 708, 693, 697, 702, 694, 700, 706, 699, 695, 706, 714, 704, 700, 695, 697, 707, 704]
S.ToArray 742.5 ms -> [742, 743, 733, 745, 741, 724, 738, 745, 728, 732, 740, 727, 739, 740, 726, 744, 758, 732, 744, 745, 730, 739, 738, 723, 745, 757, 729, 741, 736, 724, 744, 756, 739, 766, 737, 725, 741, 742, 736, 748, 742, 721, 746, 1043, 806, 747, 731, 727, 742, 742, 726, 738, 746, 727, 739, 743, 730, 744, 753, 741, 739, 746, 728, 740, 744, 734, 734, 738, 731, 747, 736, 731, 765, 735, 726, 740, 743, 730, 746, 742, 725, 731, 757, 734, 738, 741, 732, 747, 744, 721, 742, 741, 727, 745, 740, 730, 747, 760, 737, 740]

C1.ToList 32.34 ms -> [35, 31, 31, 31, 32, 31, 31, 31, 31, 31, 31, 31, 31, 31, 33, 32, 31, 31, 31, 31, 30, 32, 31, 31, 31, 31, 32, 30, 31, 31, 31, 30, 32, 31, 31, 31, 36, 31, 31, 31, 32, 30, 31, 32, 31, 31, 31, 31, 31, 32, 31, 31, 31, 31, 33, 32, 31, 32, 31, 31, 33, 31, 31, 31, 31, 31, 32, 31, 32, 31, 34, 38, 68, 42, 79, 33, 31, 31, 31, 31, 31, 30, 30, 30, 30, 31, 31, 31, 31, 32, 31, 32, 31, 31, 31, 32, 33, 33, 31, 31]
C1.ToArray 56.32 ms -> [57, 56, 59, 54, 54, 55, 56, 57, 54, 54, 55, 55, 57, 56, 59, 57, 56, 58, 56, 56, 54, 56, 57, 55, 55, 55, 57, 58, 57, 58, 55, 55, 56, 55, 57, 56, 56, 59, 56, 56, 56, 56, 58, 56, 57, 56, 56, 57, 56, 55, 56, 56, 56, 59, 56, 56, 56, 55, 55, 54, 55, 54, 57, 56, 56, 56, 55, 55, 56, 56, 56, 59, 56, 56, 57, 56, 57, 56, 56, 56, 56, 62, 55, 56, 56, 56, 69, 57, 58, 56, 57, 58, 56, 57, 56, 56, 56, 56, 56, 56]
S1.ToList 88.69 ms -> [96, 90, 90, 89, 91, 88, 89, 90, 96, 89, 89, 89, 90, 90, 90, 89, 90, 90, 89, 90, 89, 91, 89, 91, 89, 91, 89, 90, 90, 89, 87, 88, 87, 88, 87, 87, 87, 87, 88, 88, 87, 87, 89, 87, 87, 87, 91, 88, 87, 86, 89, 87, 90, 89, 89, 90, 89, 87, 87, 87, 86, 87, 88, 90, 88, 87, 87, 92, 87, 87, 88, 88, 88, 86, 86, 87, 88, 87, 87, 87, 89, 87, 89, 87, 90, 89, 89, 89, 91, 89, 90, 89, 90, 88, 90, 90, 90, 88, 89, 89]
S1.ToArray 143.26 ms -> [130, 129, 130, 131, 133, 130, 131, 130, 135, 137, 130, 136, 132, 131, 130, 131, 132, 130, 132, 136, 130, 131, 157, 153, 194, 364, 176, 189, 203, 194, 189, 192, 183, 140, 142, 147, 145, 134, 159, 158, 142, 167, 130, 143, 145, 144, 160, 154, 156, 153, 153, 164, 142, 145, 137, 134, 145, 143, 142, 135, 133, 133, 135, 134, 134, 139, 139, 133, 134, 141, 133, 132, 133, 132, 133, 131, 135, 132, 133, 132, 128, 128, 130, 132, 129, 129, 129, 129, 129, 128, 134, 129, 129, 129, 129, 128, 128, 137, 130, 131]

C2.ToList 3.25 ms -> [5, 3, 3, 3, 3, 4, 3, 4, 3, 4, 3, 3, 3, 4, 4, 3, 3, 3, 4, 3, 3, 3, 3, 4, 3, 4, 3, 4, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 3, 4, 3, 3, 3, 3, 3, 4, 3, 4, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 4, 3, 3, 3, 4, 3, 3, 4, 3, 3, 3, 4, 3, 3, 3, 3, 3]
C2.ToArray 3.37 ms -> [4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 5, 4, 9, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 4, 4, 3, 3, 4, 4, 3, 3, 3, 4, 3, 3, 3, 3, 4, 3, 3, 3, 3, 3, 4, 4, 3, 4, 4, 3, 3, 4, 3, 3, 4, 3, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 4, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 4, 3, 4, 3, 3, 3]
S2.ToList 37.72 ms -> [38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 40, 38, 38, 39, 39, 38, 38, 38, 38, 37, 37, 37, 37, 39, 37, 37, 39, 38, 37, 37, 37, 37, 39, 38, 37, 37, 38, 37, 38, 37, 37, 38, 37, 37, 37, 38, 37, 37, 36, 37, 38, 37, 39, 37, 39, 38, 37, 38, 38, 38, 38, 38, 38, 37, 38, 38, 38, 38, 38, 37, 38, 37, 37, 38, 37, 37, 39, 41, 37, 38, 38, 37, 37, 37, 37, 38, 37, 37, 37, 40, 37, 37, 37, 37, 39, 38]
S2.ToArray 38.86 ms -> [39, 37, 39, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, 39, 38, 38, 38, 38, 38, 38, 38, 38, 39, 39, 38, 38, 38, 39, 37, 38, 38, 38, 38, 38, 37, 37, 38, 37, 37, 38, 38, 40, 38, 38, 38, 38, 38, 39, 38, 38, 39, 38, 38, 39, 38, 38, 40, 38, 39, 38, 38, 39, 38, 38, 38, 38, 38, 39, 38, 38, 38, 39, 39, 37, 38, 38, 39, 71, 78, 37, 37, 37, 39, 38, 38, 39, 38, 38, 38, 38, 38, 39, 38, 38, 38, 39, 38, 38, 38]

group2:

C.ToList 756.81 ms
C.ToArray 774.21 ms
S.ToList 709.7 ms
S.ToArray 753.51 ms

C1.ToList 32.06 ms
C1.ToArray 56.58 ms
S1.ToList 89.43 ms
S1.ToArray 132.85 ms

C2.ToList 3.45 ms
C2.ToArray 3.36 ms
S2.ToList 41.43 ms
S2.ToArray 40.84 ms

Gruppo 3:

C.ToList 756.64 ms
C.ToArray 771.56 ms
S.ToList 705.42 ms
S.ToArray 749.59 ms

C1.ToList 31.45 ms
C1.ToArray 57.03 ms
S1.ToList 91.26 ms
S1.ToArray 129.77 ms

C2.ToList 3.26 ms
C2.ToArray 3.29 ms
S2.ToList 41.57 ms
S2.ToArray 40.69 ms

Group4:

C.ToList 729.65 ms -> [749, 730, 721, 719, 723, 743, 721, 724, 727, 722, 716, 725, 723, 726, 718, 722, 731, 722, 723, 725, 723, 722, 728, 726, 728, 718, 726, 1088, 788, 737, 729, 710, 730, 728, 717, 723, 728, 721, 722, 728, 722, 736, 723, 729, 732, 724, 726, 727, 728, 728, 726, 726, 725, 727, 725, 728, 728, 718, 724, 725, 726, 724, 726, 729, 727, 722, 722, 725, 725, 728, 724, 727, 738, 717, 726, 723, 725, 725, 727, 724, 720, 726, 726, 723, 727, 730, 723, 721, 725, 727, 727, 733, 720, 722, 722, 725, 722, 725, 728, 726]
C.ToArray 788.36 ms -> [748, 740, 742, 797, 1090, 774, 781, 787, 784, 786, 786, 782, 781, 781, 784, 783, 783, 781, 783, 787, 783, 784, 775, 789, 784, 785, 778, 774, 781, 783, 786, 781, 780, 788, 778, 785, 777, 781, 786, 782, 781, 787, 782, 787, 784, 773, 783, 782, 781, 777, 783, 781, 785, 788, 777, 776, 784, 784, 783, 789, 778, 781, 791, 768, 779, 783, 781, 787, 786, 781, 784, 781, 785, 781, 780, 809, 1155, 780, 790, 789, 783, 776, 785, 783, 786, 787, 782, 782, 787, 777, 779, 784, 783, 776, 786, 775, 782, 779, 784, 784]
S.ToList 705.54 ms -> [690, 705, 709, 708, 702, 707, 703, 696, 703, 702, 700, 703, 700, 707, 705, 699, 697, 703, 695, 698, 707, 697, 711, 710, 699, 700, 708, 707, 693, 710, 704, 691, 702, 700, 703, 700, 705, 700, 703, 695, 709, 705, 698, 699, 709, 700, 699, 704, 691, 705, 703, 700, 708, 1048, 710, 706, 706, 692, 702, 705, 695, 701, 710, 697, 698, 706, 705, 707, 707, 695, 698, 704, 698, 699, 705, 698, 703, 702, 701, 697, 702, 702, 704, 703, 699, 707, 703, 705, 701, 717, 698, 695, 713, 696, 708, 705, 697, 699, 700, 698]
S.ToArray 745.01 ms -> [751, 743, 727, 734, 736, 745, 739, 750, 739, 750, 758, 739, 744, 738, 730, 744, 745, 739, 744, 750, 733, 735, 743, 731, 749, 748, 727, 746, 749, 731, 737, 803, 1059, 756, 769, 748, 740, 745, 741, 746, 749, 732, 741, 742, 732, 744, 746, 737, 742, 739, 733, 744, 741, 729, 746, 760, 725, 741, 764, 739, 750, 751, 727, 745, 738, 727, 735, 741, 720, 736, 740, 733, 741, 746, 731, 749, 756, 740, 738, 736, 732, 741, 741, 733, 741, 744, 736, 742, 742, 735, 743, 746, 729, 748, 765, 743, 734, 742, 728, 749]

C1.ToList 32.27 ms -> [36, 31, 31, 32, 31, 32, 31, 30, 32, 30, 30, 30, 34, 32, 31, 31, 31, 31, 31, 31, 31, 32, 38, 51, 68, 57, 35, 30, 31, 31, 30, 30, 33, 30, 31, 34, 31, 34, 32, 31, 31, 31, 31, 32, 30, 30, 31, 30, 31, 31, 32, 31, 31, 31, 32, 31, 31, 31, 32, 31, 33, 31, 31, 32, 30, 30, 30, 30, 30, 33, 30, 33, 32, 31, 30, 31, 31, 32, 32, 31, 35, 31, 34, 31, 31, 32, 31, 31, 32, 31, 32, 31, 31, 35, 31, 31, 31, 31, 31, 32]
C1.ToArray 56.72 ms -> [58, 56, 57, 57, 59, 58, 58, 57, 56, 59, 57, 55, 55, 54, 56, 55, 56, 56, 57, 59, 56, 55, 58, 56, 55, 55, 55, 55, 58, 58, 55, 57, 57, 56, 57, 57, 57, 57, 59, 59, 56, 57, 56, 57, 57, 56, 57, 59, 58, 56, 57, 57, 57, 58, 56, 56, 59, 56, 59, 57, 57, 57, 57, 59, 57, 56, 57, 56, 58, 56, 57, 56, 57, 59, 55, 58, 55, 55, 56, 56, 56, 56, 56, 56, 56, 56, 56, 57, 56, 56, 57, 56, 56, 57, 58, 57, 57, 57, 57, 57]
S1.ToList 90.72 ms -> [95, 90, 90, 89, 89, 89, 91, 89, 89, 87, 91, 89, 89, 89, 91, 89, 89, 89, 90, 89, 89, 90, 88, 89, 88, 90, 89, 90, 89, 89, 90, 90, 89, 89, 90, 91, 89, 91, 89, 90, 89, 89, 90, 91, 89, 89, 89, 89, 89, 89, 90, 89, 89, 89, 90, 89, 90, 89, 91, 89, 90, 89, 90, 89, 90, 89, 96, 89, 90, 89, 89, 89, 89, 89, 90, 89, 89, 89, 90, 87, 89, 90, 90, 91, 89, 91, 89, 89, 90, 91, 90, 89, 93, 144, 149, 90, 90, 89, 89, 89]
S1.ToArray 131.4 ms -> [130, 128, 127, 134, 129, 129, 130, 136, 131, 130, 132, 132, 133, 131, 132, 131, 133, 132, 130, 131, 132, 131, 130, 133, 133, 130, 130, 131, 131, 131, 132, 134, 131, 131, 132, 131, 132, 131, 134, 131, 131, 130, 131, 131, 130, 132, 129, 131, 131, 131, 132, 131, 133, 134, 131, 131, 132, 132, 131, 133, 131, 131, 130, 133, 131, 130, 134, 132, 131, 132, 132, 131, 131, 134, 131, 131, 132, 132, 131, 130, 138, 130, 130, 131, 132, 132, 130, 134, 131, 131, 132, 131, 130, 132, 133, 131, 131, 131, 130, 131]

C2.ToList 3.21 ms -> [4, 3, 3, 3, 4, 3, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 3, 3, 4, 3, 3, 3, 3, 3, 4, 3, 4, 3, 3, 3, 3, 3, 4, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 4, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 3, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 4, 3, 3, 3]
C2.ToArray 3.22 ms -> [4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 4, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 4, 3, 4, 3, 3, 3, 4, 3, 4, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 4, 3, 4, 3, 3, 3, 3, 3, 4, 3, 3, 3, 4, 3, 4, 3, 3, 3, 3, 4]
S2.ToList 41.46 ms -> [42, 40, 41, 40, 42, 40, 40, 40, 40, 40, 40, 40, 40, 41, 40, 40, 41, 40, 40, 40, 39, 41, 41, 39, 40, 40, 43, 40, 39, 40, 40, 40, 40, 40, 40, 41, 40, 40, 40, 43, 40, 43, 75, 76, 47, 39, 40, 40, 40, 40, 42, 40, 41, 40, 40, 40, 44, 41, 40, 42, 42, 40, 41, 41, 41, 41, 41, 40, 41, 41, 41, 41, 42, 41, 40, 41, 41, 42, 42, 41, 40, 41, 41, 41, 41, 41, 40, 42, 40, 42, 41, 41, 41, 43, 41, 41, 41, 41, 42, 41]
S2.ToArray 41.14 ms -> [42, 41, 41, 40, 40, 40, 40, 41, 41, 42, 41, 42, 41, 41, 41, 42, 41, 41, 42, 41, 41, 41, 41, 41, 42, 40, 41, 40, 42, 40, 42, 41, 40, 42, 41, 41, 43, 42, 41, 41, 41, 41, 41, 41, 41, 41, 41, 41, 40, 40, 41, 41, 41, 40, 42, 41, 41, 41, 41, 41, 40, 41, 41, 42, 41, 41, 41, 42, 41, 41, 41, 41, 41, 41, 42, 42, 42, 41, 45, 46, 41, 40, 41, 41, 42, 41, 41, 41, 41, 41, 41, 40, 41, 43, 40, 40, 40, 40, 43, 41]

Group5:

C.ToList 757.06 ms -> [770, 752, 752, 751, 778, 763, 761, 763, 747, 758, 748, 747, 754, 749, 752, 753, 756, 762, 750, 753, 756, 749, 755, 757, 755, 756, 755, 744, 753, 758, 747, 751, 759, 751, 761, 755, 746, 752, 752, 749, 746, 752, 753, 755, 752, 755, 754, 754, 966, 937, 749, 759, 748, 747, 754, 749, 755, 750, 746, 754, 757, 752, 753, 745, 758, 755, 761, 753, 751, 755, 755, 752, 746, 756, 755, 746, 742, 751, 751, 749, 752, 751, 756, 756, 755, 742, 749, 754, 749, 756, 753, 751, 754, 752, 751, 754, 753, 749, 755, 756]
C.ToArray 772.8 ms -> [766, 772, 755, 763, 758, 767, 763, 762, 761, 768, 769, 763, 770, 757, 765, 760, 766, 759, 764, 761, 760, 777, 1102, 881, 759, 765, 758, 762, 772, 761, 758, 757, 765, 769, 769, 761, 762, 762, 763, 760, 770, 764, 760, 768, 758, 766, 763, 770, 769, 761, 764, 761, 761, 767, 761, 762, 764, 757, 765, 766, 767, 771, 753, 762, 769, 768, 759, 764, 764, 760, 763, 763, 763, 763, 763, 767, 761, 771, 760, 765, 760, 758, 768, 770, 751, 771, 767, 771, 765, 763, 760, 765, 765, 769, 767, 767, 1193, 774, 767, 764]
S.ToList 704.73 ms -> [682, 708, 705, 699, 705, 704, 695, 703, 702, 699, 701, 708, 699, 702, 703, 701, 701, 699, 701, 707, 707, 700, 701, 705, 700, 697, 706, 702, 701, 706, 699, 692, 702, 697, 707, 704, 697, 698, 699, 699, 702, 703, 698, 697, 702, 703, 702, 704, 694, 697, 707, 695, 711, 710, 700, 693, 703, 699, 699, 706, 698, 701, 703, 704, 698, 706, 700, 704, 701, 699, 702, 705, 694, 698, 709, 736, 1053, 704, 694, 700, 698, 696, 701, 700, 700, 706, 706, 692, 698, 707, 703, 695, 703, 699, 694, 708, 695, 694, 706, 695]
S.ToArray 744.17 ms -> [746, 740, 725, 740, 739, 731, 746, 760, 735, 738, 740, 734, 744, 748, 737, 744, 745, 727, 736, 738, 728, 743, 745, 735, 748, 760, 739, 748, 762, 742, 741, 747, 733, 746, 758, 742, 742, 741, 724, 744, 747, 727, 740, 740, 729, 742, 757, 741, 740, 742, 726, 739, 746, 1133, 749, 737, 730, 740, 747, 733, 747, 752, 731, 747, 742, 730, 741, 749, 731, 749, 743, 730, 747, 742, 731, 737, 745, 734, 739, 735, 727, 743, 752, 731, 744, 742, 729, 740, 746, 731, 739, 746, 733, 745, 743, 733, 739, 742, 727, 737]

C1.ToList 31.71 ms -> [35, 32, 32, 30, 31, 33, 31, 32, 32, 31, 31, 32, 32, 33, 32, 31, 31, 32, 31, 32, 32, 32, 31, 32, 33, 32, 31, 31, 31, 32, 31, 34, 31, 31, 32, 33, 32, 32, 31, 32, 34, 32, 31, 32, 33, 31, 32, 32, 31, 32, 32, 32, 32, 32, 32, 31, 31, 32, 31, 33, 30, 31, 32, 30, 30, 33, 32, 32, 34, 31, 31, 31, 31, 32, 31, 31, 31, 31, 32, 31, 31, 33, 31, 32, 32, 32, 33, 32, 31, 31, 31, 31, 31, 32, 32, 33, 32, 31, 31, 32]
C1.ToArray 59.53 ms -> [63, 57, 58, 58, 57, 59, 59, 57, 60, 131, 127, 67, 58, 56, 59, 56, 57, 58, 58, 58, 57, 59, 60, 57, 57, 59, 60, 57, 57, 57, 58, 58, 58, 58, 57, 57, 61, 57, 58, 57, 57, 57, 57, 57, 58, 58, 58, 58, 57, 58, 59, 57, 58, 57, 57, 59, 58, 58, 59, 57, 59, 57, 56, 56, 59, 56, 56, 59, 57, 58, 58, 58, 57, 58, 59, 59, 58, 57, 58, 62, 65, 57, 57, 57, 58, 60, 59, 58, 59, 57, 58, 57, 58, 59, 58, 58, 58, 59, 60, 58]
S1.ToList 82.78 ms -> [87, 82, 83, 83, 82, 82, 83, 84, 82, 83, 84, 84, 84, 82, 82, 84, 82, 84, 83, 84, 82, 82, 82, 81, 83, 83, 83, 84, 84, 82, 82, 83, 83, 83, 82, 83, 85, 83, 82, 82, 84, 82, 82, 83, 83, 83, 82, 82, 82, 83, 82, 83, 82, 84, 82, 83, 82, 83, 82, 82, 82, 84, 82, 83, 82, 82, 86, 83, 83, 82, 83, 83, 83, 82, 84, 82, 83, 81, 82, 82, 82, 82, 83, 83, 83, 82, 83, 84, 83, 82, 83, 83, 83, 82, 83, 84, 82, 82, 83, 83]
S1.ToArray 122.3 ms -> [122, 119, 119, 120, 119, 120, 120, 121, 119, 119, 122, 120, 120, 120, 122, 120, 123, 120, 120, 120, 121, 123, 120, 120, 120, 121, 120, 121, 122, 120, 123, 119, 121, 118, 121, 120, 120, 120, 119, 124, 119, 121, 119, 120, 120, 120, 120, 120, 122, 121, 123, 230, 203, 123, 119, 119, 122, 119, 120, 120, 120, 122, 120, 121, 120, 121, 120, 121, 120, 121, 120, 120, 120, 121, 122, 121, 123, 119, 119, 119, 119, 121, 120, 120, 120, 122, 121, 122, 119, 120, 120, 121, 121, 120, 121, 120, 121, 118, 118, 118]

C2.ToList 3.43 ms -> [5, 3, 4, 4, 4, 3, 4, 4, 4, 4, 4, 3, 3, 3, 4, 3, 3, 3, 4, 3, 4, 3, 4, 3, 4, 4, 3, 3, 3, 3, 4, 3, 3, 3, 4, 3, 4, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 6, 4, 4, 3, 3, 4, 3, 3, 4, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 3, 3, 3, 3, 4, 3, 4, 4, 4, 3, 4, 4, 3, 4, 4, 4, 4, 4, 4, 3, 3, 3, 4, 4, 3, 3, 3, 3]
C2.ToArray 3.48 ms -> [3, 3, 3, 3, 4, 4, 3, 4, 4, 4, 3, 4, 3, 3, 4, 3, 3, 4, 3, 4, 3, 3, 3, 4, 3, 3, 3, 4, 3, 3, 3, 3, 4, 3, 4, 3, 3, 4, 3, 4, 3, 3, 3, 3, 4, 3, 3, 3, 3, 4, 3, 4, 3, 3, 4, 3, 3, 4, 3, 3, 4, 4, 3, 3, 4, 3, 4, 4, 3, 4, 4, 4, 4, 4, 3, 3, 3, 4, 4, 3, 4, 4, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 4, 4, 4, 4, 3]
S2.ToList 41.47 ms -> [41, 41, 49, 67, 82, 41, 41, 40, 40, 40, 40, 40, 41, 40, 40, 40, 40, 40, 41, 40, 42, 42, 40, 40, 41, 41, 41, 40, 41, 40, 41, 40, 41, 40, 42, 41, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 41, 41, 41, 41, 41, 42, 41, 41, 41, 42, 40, 41, 40, 40, 40, 42, 40, 41, 42, 41, 42, 41, 42, 40, 41, 41, 41, 41, 41, 41, 41, 41, 40, 41, 40, 41, 41, 41, 40, 41, 41, 40, 40, 41, 41, 41, 41, 41, 43, 40, 40, 41, 42, 41]
S2.ToArray 40.62 ms -> [42, 41, 44, 40, 40, 40, 40, 41, 41, 40, 41, 41, 41, 40, 41, 41, 40, 41, 41, 40, 41, 40, 40, 41, 42, 41, 41, 41, 40, 40, 40, 40, 40, 41, 41, 42, 40, 41, 41, 41, 41, 41, 40, 42, 40, 40, 41, 41, 41, 40, 41, 40, 40, 40, 40, 40, 41, 40, 40, 41, 40, 40, 40, 40, 41, 40, 41, 41, 41, 40, 41, 41, 40, 41, 40, 41, 42, 40, 41, 41, 42, 41, 41, 40, 41, 40, 41, 40, 41, 41, 40, 40, 40, 41, 41, 40, 40, 40, 40, 40]

A causa del limite di stackoverflow alla quantità di caratteri della risposta, gli elenchi di esempio di Group2 e Group3 vengono omessi.

Come puoi vedere, non è davvero importante usare ToListo ToArrynella maggior parte dei casi.

Durante l'elaborazione di IEnumerable<T>oggetti calcolati in fase di runtime , se il carico portato dal calcolo è pesante rispetto alle operazioni di allocazione e copia della memoria di ToListe ToArray, la disparità è insignificante ( C.ToList vs C.ToArraye S.ToList vs S.ToArray).

La differenza può essere osservata solo su IEnumerable<T>oggetti ( C1.ToList vs C1.ToArraye S1.ToList vs S1.ToArray) non calcolati in runtime . Ma la differenza assoluta (<60 ms) è ancora accettabile su un milione di piccoli oggetti IEnumerable<T>. In effetti, la differenza è decisa dall'attuazione Enumerator<T>di IEnumerable<T>. Quindi, se il tuo programma è davvero molto sensibile su questo, devi profilo, profilo, profilo ! Alla fine probabilmente troverai che il collo di bottiglia non è attivo ToListo ToArray, ma i dettagli degli enumeratori.

E, il risultato di C2.ToList vs C2.ToArraye S2.ToList vs S2.ToArrayspettacoli che, davvero non c'è bisogno di preoccuparsi ToListo ToArraysulla non-runtime-calcolata ICollection<T>oggetti.

Naturalmente, questi sono solo risultati sulla mia macchina, il tempo effettivo impiegato da queste operazioni su macchine diverse non sarà lo stesso, puoi scoprirlo sulla tua macchina usando il codice sopra.

L'unica ragione per cui devi fare una scelta è che hai esigenze specifiche List<T>o T[], come descritto dalla risposta di @Jeppe Stig Nielsen .


1

Per chiunque sia interessato a utilizzare questo risultato in un altro Linq-to-sql come

from q in context.MyTable
where myListOrArray.Contains(q.someID)
select q;

quindi l'SQL generato è lo stesso sia che tu abbia usato un Elenco o un array per myListOrArray. Ora so che alcuni potrebbero chiedersi perché anche enumerarli prima di questa affermazione, ma c'è una differenza tra l'SQL generato da un IQueryable vs (List o Array).

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.