¿Es mejor llamar a ToList () o ToArray () en consultas LINQ?

519

A menudo me encuentro con el caso en el que quiero evaluar una consulta justo donde la declaro. Esto generalmente se debe a que necesito repetirlo varias veces y es costoso calcularlo. Por ejemplo:

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

Esto funciona bien Pero si no voy a modificar el resultado, entonces podría llamar en ToArray()lugar de ToList().

Sin embargo, me pregunto si ToArray()se implementa por primera llamada ToList()y, por lo tanto, es menos eficiente en memoria que solo llamar ToList().

¿Estoy loco? ¿Debo llamar ToArray(), seguro y seguro sabiendo que la memoria no se asignará dos veces?

Frank Krueger
fuente
10
Si alguna vez quieres saber qué sucede detrás de las cortinas en .NET, realmente recomiendo .NET Reflector
David Hedlund el
32
@DavidHedlund Recomiendo el código fuente .net .
Gqqnbig
1
No estoy de acuerdo en que stackoverflow.com/questions/6750447/c-toarray-performance sea ​​un duplicado de esta pregunta, aunque haya una relación importante. Tanto el uso de memoria (esta pregunta) como el rendimiento (otra pregunta) son consideraciones interesantes y no triviales. Se pueden describir por separado, pero ambos deben tener en cuenta la decisión de elegir uno sobre el otro. No puedo recomendar ninguna de las respuestas a esta u otra pregunta como exhaustiva. Hay varias respuestas que, cuando se toman juntas, proporcionan una discusión bastante completa sobre cómo elegir una sobre la otra.
Steve
1
@Gqqnbig: ¡el comentario más útil de todos! Gracias :-)
Mark Cooper

Respuestas:

366

A menos que simplemente necesite una matriz para cumplir con otras restricciones que debe usar ToList. En la mayoría de los escenarios ToArrayse asignará más memoria que ToList.

Ambos usan matrices para el almacenamiento, pero ToListtienen una restricción más flexible. Necesita que la matriz sea al menos tan grande como el número de elementos en la colección. Si la matriz es más grande, eso no es un problema. Sin embargo, ToArraynecesita que la matriz tenga un tamaño exactamente igual al número de elementos.

Para cumplir con esta restricción, a ToArraymenudo se hace una asignación más que ToList. Una vez que tiene una matriz que es lo suficientemente grande, asigna una matriz que es exactamente del tamaño correcto y copia los elementos nuevamente en esa matriz. El único momento en que puede evitar esto es cuando el algoritmo de crecimiento de la matriz coincide con la cantidad de elementos que deben almacenarse (definitivamente en la minoría).

EDITAR

Un par de personas me han preguntado sobre la consecuencia de tener la memoria extra no utilizada en el List<T>valor.

Esta es una preocupación valida. Si la colección creada es de larga duración, nunca se modifica después de ser creada y tiene una alta probabilidad de aterrizar en el montón Gen2, entonces es mejor que tome la asignación adicional de ToArrayantemano.

En general, creo que este es el caso más raro. Es mucho más común ver muchas ToArrayllamadas que se pasan inmediatamente a otros usos de memoria de corta duración, en cuyo caso ToListes demostrablemente mejor.

La clave aquí es perfilar, perfilar y luego perfilar un poco más.

JaredPar
fuente
14
Por otro lado, ¿no sería elegible para la recolección de basura la memoria extra asignada para el trabajo preliminar de crear la matriz, mientras que la sobrecarga adicional para la Lista permanecería? Yo digo que sea más simple. Si necesita agregar o eliminar elementos, hay una herramienta para eso. Si no lo hace, hay una herramienta diferente para eso. Usa el que tenga sentido. Si más adelante, descubre un problema con la memoria y el rendimiento, y esto es todo , cámbielo.
Anthony Pegram
1
@AnthonyPegram sí, es una consideración válida para hacer. Si el valor se está utilizando en el almacenamiento a largo plazo, no se modificará y potencialmente pasará a Gen 2, entonces es mejor que pague la asignación adicional ahora en lugar de contaminar el montón Gen 2. IME aunque rara vez veo esto. Es mucho más común ver que ToArray se pasa inmediatamente a otra consulta LINQ de corta duración.
JaredPar
2
@AnthonyPegram actualicé mi respuesta para incluir este lado de la discusión
JaredPar
8
@JaredPar No entiendo cómo ToArraypuede asignar más memoria si necesita el tamaño exacto de las ubicaciones donde ToList<>obviamente tiene sus ubicaciones de repuesto automáticas. (autoincrease)
Royi Namir
55
@RoyiNamir porque ToArray primero realiza las asignaciones de estilo ToList con sobrecarga, luego realiza una asignación de tamaño exacto adicional.
Timbo
169

La diferencia de rendimiento será insignificante, ya que List<T>se implementa como una matriz de tamaño dinámico. Llamar ya sea ToArray()(que usa una Buffer<T>clase interna para hacer crecer la matriz) o ToList()(que llama al List<T>(IEnumerable<T>)constructor) terminará siendo una cuestión de ponerlos en una matriz y hacer crecer la matriz hasta que se ajuste a todos.

Si desea una confirmación concreta de este hecho, consulte la implementación de los métodos en cuestión en Reflector: verá que se reducen a un código casi idéntico.

mqp
fuente
2
Un hecho interesante que encontré es que para las consultas correlacionadas causadas por el uso de un grupo definido a través de una unión grupal en su proyección, Linq to SQL agrega otra subconsulta para recuperar el recuento de ese grupo. Supongo que esto significa que en estos casos el tamaño de la colección se conocerá antes de que se recuperen los elementos y, por lo tanto, se podría crear directamente una matriz de tamaño exacto que ahorre recursos de procesamiento y memoria mientras se materializan los resultados.
jpierson
133
Si el recuento se conoce de antemano, el rendimiento es idéntico. Sin embargo, si el Conteo no se conoce de antemano, la única diferencia entre ToArray()y ToList()es que el primero tiene que recortar el exceso, lo que implica copiar toda la matriz, mientras que el segundo no recorta el exceso, sino que usa un promedio de 25 % más memoria. Esto solo tendrá implicaciones si el tipo de datos es grande struct. Solo comida para pensar.
Scott Rippey
99
@EldritchConundrum El 25% proviene de esta lógica: si se desconoce el número de elementos, entonces la llamada ToListo ToArraycomenzará creando un pequeño búfer. Cuando ese búfer se llena, duplica la capacidad del búfer y continúa. Como la capacidad siempre se duplica, el búfer no utilizado siempre estará entre 0% y 50%.
Scott Rippey
2
@ScottRippey Acabo de buscar la fuente de la nueva Lista de la fuente IEnumerable, y comprueba si IEnumerable es una ICollection, y si lo es, comienza asignando una matriz con el tamaño exacto necesario de la propiedad Count, así que esto sería el caso donde ToList () definitivamente sería más rápido. Una respuesta completa podría incluir ese hecho, aunque no creo que sea el caso más común.
AndyClaw
3
@AndyClaw Ambos Listy Bufferverificarán ICollection, en cuyo caso el rendimiento será idéntico.
Scott Rippey
54

(siete años despues...)

Un par de otras (buenas) respuestas se han concentrado en las diferencias de rendimiento microscópico que ocurrirán.

Esta publicación es solo un complemento para mencionar la diferencia semántica que existe entre la IEnumerator<T>producida por un array ( T[]) en comparación con la devuelta por a List<T>.

Mejor ilustrado con el ejemplo:

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);
}

El código anterior se ejecutará sin excepción y produce el resultado:

1
2
3
4 4
5 5
6 6
7 7
8
900
10

Esto muestra que el IEnumarator<int>devuelto por un int[]no realiza un seguimiento de si la matriz se ha modificado desde la creación del enumerador.

Tenga en cuenta que he declarado la variable local sourcecomo un IList<int>. De esa manera me aseguro de que el compilador de C # no optimice la foreachdeclaración en algo que sea equivalente a un for (var idx = 0; idx < source.Length; idx++) { /* ... */ }bucle. Esto es algo que el compilador de C # podría hacer si lo uso var source = ...;en su lugar. En mi versión actual de .NET Framework, el enumerador real utilizado aquí es un tipo de referencia no público, System.SZArrayHelper+SZGenericArrayEnumerator`1[System.Int32]pero, por supuesto, este es un detalle de implementación.

Ahora, si cambio .ToArray()hacia .ToList(), solo me dan:

1
2
3
4 4
5 5

seguido de una System.InvalidOperationExceptionexplosión diciendo:

La colección fue modificada; la operación de enumeración puede no ejecutarse.

El enumerador subyacente en este caso es el tipo de valor mutable público System.Collections.Generic.List`1+Enumerator[System.Int32](encuadrado dentro de un IEnumerator<int>cuadro en este caso porque lo uso IList<int>).

En conclusión, el enumerador producido por unList<T>realizaunseguimiento de si la lista cambia durante la enumeración, mientras que el enumerador producido porT[]no. Considere esta diferencia al elegir entre.ToList()y.ToArray().

Las personas a menudo agregan un extra .ToArray() o .ToList()eluden una colección que realiza un seguimiento de si se modificó durante la vida de un enumerador.

(Si alguien quiere saber cómo la List<>hace un seguimiento de si la colección se ha modificado, no es un campo privado _versionen esta clase que se cambia cada vez que el List<>se actualiza.)

Jeppe Stig Nielsen
fuente
28

Estoy de acuerdo con @mquander en que la diferencia de rendimiento debería ser insignificante. Sin embargo, quería compararlo para estar seguro, así que lo hice y es 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

Cada fuente matriz / Lista tenía 1000 elementos. Entonces puede ver que tanto las diferencias de tiempo como de memoria son insignificantes.

Mi conclusión: también podría usar ToList () , ya que a List<T>proporciona más funcionalidad que una matriz, a menos que realmente le importen unos pocos bytes de memoria.

EMP
fuente
1
Me pregunto si este resultado sería diferente si structusaras un tipo o clase grande en lugar de uno primitivo.
Scott Rippey
12
Lista <T> .ToList ???? Que sentido ? Será mejor que intente darle un IEnumerable, que no implementa la interfaz ICollection.
Grigory
8
Quería asegurarme de medir solo el tiempo de la llamada ToListo ToArrayno la enumeración de ninguno IEnumerable. List <T> .ToList () todavía crea una nueva List <T>, no simplemente "devuelve esto".
EMP
23
-1 Los comportamientos ToArray()y ToList()difieren demasiado cuando se les proporciona un ICollection<T>parámetro: solo realizan una única asignación y una sola operación de copia. Ambos List<T>e Arrayimplementar ICollection<T>, por lo que sus puntos de referencia no son válidos en absoluto.
Mohammad Dehghan
1
Para cualquier persona interesada, publiqué mi propio punto de referencia como una respuesta separada . Se utiliza .Select(i => i)para evitar el ICollection<T>problema de implementación, e incluye un grupo de control para ver cuánto tiempo se pasa iterando sobre la fuente IEnumerable<>en primer lugar.
StriplingWarrior
19

ToList()generalmente se prefiere si lo usa en IEnumerable<T>(desde ORM, por ejemplo). Si la longitud de la secuencia no se conoce al principio, ToArray()crea una colección de longitud dinámica como Lista y luego la convierte en una matriz, lo que lleva más tiempo.

Vitaliy Ulantikov
fuente
26
He decidido que la legibilidad supera el rendimiento en este caso. Ahora solo uso ToList cuando espero continuar agregando elementos. En todos los demás casos (la mayoría de los casos), uso ToArray. Pero gracias por el aporte!
Frank Krueger
55
Mirando en ILSpy, Enumerable.ToArray()llamadas new Buffer<TSource>(source).ToArray(). En el constructor Buffer, si la fuente implementa ICollection, entonces llama a source.CopyTo (items, 0), y luego .ToArray () devuelve la matriz de elementos internos directamente. Por lo tanto, no hay conversión que tome tiempo extra en ese caso. Si la fuente no implementa ICollection, ToArray dará como resultado una copia de matriz para recortar las ubicaciones adicionales no utilizadas desde el final de la matriz como se describe en el comentario anterior de Scott Rippey.
BrandonAGr
19

La memoria siempre se asignará dos veces, o algo parecido. Como no puede cambiar el tamaño de una matriz, ambos métodos utilizarán algún tipo de mecanismo para recopilar los datos en una colección creciente. (Bueno, la Lista es una colección creciente en sí misma).

La lista utiliza una matriz como almacenamiento interno y duplica la capacidad cuando es necesario. Esto significa que, en promedio, 2/3 de los artículos han sido reasignados al menos una vez, la mitad de los reasignados al menos dos veces, la mitad de esos al menos tres veces, y así sucesivamente. Eso significa que cada elemento ha sido reasignado en promedio 1.3 veces, lo que no supone una sobrecarga.

Recuerde también que si está recopilando cadenas, la colección en sí solo contiene las referencias a las cadenas, las cadenas en sí no se reasignan.

Guffa
fuente
Esto puede ser una pregunta ignorante, pero ¿no asume la lógica 2/3, 1/3, 1/6 que usted describe que la matriz de la Lista se puede extender en su lugar? Es decir, ¿hay espacio libre al final de la matriz para que no sea necesario mover la asignación existente?
@ JonofAllTrades: No, la matriz nunca se extiende en su lugar, la administración de memoria en .NET simplemente no hace eso. Si se extendiera en su lugar, no habría necesidad de reasignar los artículos.
Guffa
Ah, ya veo: los artículos que no se reasignaron no tenían que hacerlo porque estaban en la asignación final. Todos los elementos asignados en asignaciones anteriores se mueven, pero debido a los aumentos logarítmicos en la longitud de la matriz, esta es una fracción calculable. ¡Gracias por aclararlo!
19

Es 2020 afuera y todos usan .NET Core 3.1, así que decidí ejecutar algunos puntos de referencia con Benchmark.NET.

TL; DR: ToArray () es mejor en cuanto al rendimiento y hace un mejor trabajo al transmitir la intención si no planea mutar la colección.


    [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>();
    }

Los resultados son:


    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)
Tyrrrz
fuente
1
Si no planea mutar la colección, creo que la intención se puede mostrar mejor con ToImmutableArray()(del Sistema.Colecciones.Paquete inmutable) 😉
Arturo Torres Sánchez
@ ArturoTorresSánchez es cierto, pero si la colección no se expone fuera de un método, simplemente usaría una matriz.
Tyrrrz
2
Gracias por esto. La respuesta elegida es un mero argumento y supone resultados después de ese argumento. Para hacer esto científicamente y como un extra saber cuánta diferencia hay, solo hay una forma real de saberlo.
Jonas
15

Editar : la última parte de esta respuesta no es válida. Sin embargo, el resto sigue siendo información útil, así que lo dejaré.

Sé que esta es una publicación antigua, pero después de hacer la misma pregunta e investigar un poco, he encontrado algo interesante que podría valer la pena compartir.

Primero, estoy de acuerdo con @mquander y su respuesta. Tiene razón al decir que, en cuanto al rendimiento, los dos son idénticos.

Sin embargo, he estado usando Reflector para echar un vistazo a los métodos en el System.Linq.Enumerableespacio de nombres de extensiones, y he notado una optimización muy común.
Siempre que sea posible, la IEnumerable<T>fuente se convierte IList<T>u ICollection<T>optimiza el método. Por ejemplo, mira ElementAt(int).

Curiosamente, Microsoft eligió optimizar solo para IList<T>, pero no IList. Parece que Microsoft prefiere usar la IList<T>interfaz.

System.Arraysolo implementa IList, por lo que no se beneficiará de ninguna de estas optimizaciones de extensión.
Por lo tanto, afirmo que la mejor práctica es utilizar el .ToList()método.
Si utiliza alguno de los métodos de extensión o pasa la lista a otro método, existe la posibilidad de que esté optimizado para un IList<T>.

Scott Rippey
fuente
16
Hice una prueba y descubrí algo sorprendente. ¡Una matriz implementa IList <T>! Usando Reflector para analizar System.Array solo revela una cadena de herencia de IList, ICollection, IEnumerable pero usando la reflexión en tiempo de ejecución descubrí que string [] tiene una cadena de herencia de IList, ICollection, IEnumerable, IList <string>, ICollection <string >, IEnumerable <cadena>. ¡Por lo tanto, no tengo una mejor respuesta que @mquander!
Scott Rippey
@ScottRippey Sí. La observación extraña que notó es en realidad parte de un "hack", y también tiene algunas implicaciones bastante extrañas con respecto al "tamaño fijo" y propiedades similares (con algunas inconsistencias dependiendo de cómo lo lance). Hay algunos comentarios bastante importantes sobre este tema dentro del código fuente .net. Perdón por no vincular, pero si recuerdo correctamente, es bastante fácil de encontrar (dentro de la clase de matriz). (Y también hay una gran pregunta SO que discute las inconsistencias ... en algún lugar ...> __>)
AnorZaken
@ScottRippey solo FYI Encontré esta respuesta que tiene que ver con tu comentario: stackoverflow.com/a/4482567/2063755
David Klempfner
14

Descubrí que los otros puntos de referencia que la gente ha hecho aquí carecen, así que aquí está mi oportunidad. Avíseme si encuentra algo mal con mi metodología.

/* 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

Puede descargar la secuencia de comandos LINQPad aquí .

Resultados: Rendimiento ToArray vs ToList

Al ajustar el código anterior, descubrirá que:

  1. La diferencia es menos significativa cuando se trata de matrices más pequeñas . Más iteraciones, pero matrices más pequeñas.
  2. La diferencia es menos significativa cuando se trata de ints en lugar de strings.
  3. Usar structs grande en lugar de strings lleva mucho más tiempo en general, pero realmente no cambia mucho la relación.

Esto concuerda con las conclusiones de las respuestas más votadas:

  1. Es poco probable que note una diferencia de rendimiento a menos que su código produzca muchas listas grandes de datos. (Solo hubo una diferencia de 200 ms al crear 1000 listas de 100K cadenas cada una).
  2. ToList() se ejecuta de manera más rápida y sería una mejor opción si no planea conservar los resultados durante mucho tiempo.

Actualizar

@JonHanna señaló que dependiendo de la implementación de la Selectque es posible que una ToList()o ToArray()aplicación para predecir el tamaño por delante de la colección resultante de tiempo. Reemplazar .Select(i => i)el código anterior con Where(i => true) resultados muy similares en este momento, y es más probable que lo haga independientemente de la implementación de .NET.

Benchmark usando Where en lugar de Select

StriplingWarrior
fuente
En .NET Core, ambos casos deberían ser mejores aquí que en netfx, porque se dará cuenta de que el tamaño va a ser 100000y lo usará para optimizar ambos ToList()y ToArray(), al ToArray()ser un poco más ligero porque no necesita la operación de encogimiento que necesitaría de lo contrario, cuál es el único lugar ToList()tiene la ventaja. El ejemplo en la pregunta aún se perdería, porque Whereno se puede hacer la predicción de tamaño.
Jon Hanna
@ JonHanna: Gracias por los comentarios rápidos. No sabía que .NET Core estaba haciendo esa optimización. Eso es genial. En mi código, .Select(i => i)podría ser reemplazado con .Where(i => true)para corregir eso.
StriplingWarrior
Sí, eso detendría la optimización que lo afecta en corefx. Puede ser interesante tener un tamaño que sea una potencia de dos (lo que debería dar ToArray()una ventaja) y uno que no lo es, como se indicó anteriormente, y comparar los resultados.
Jon Hanna
@ JonHanna: Curiosamente, ToArray() todavía pierde en el mejor de los casos. Con Math.Pow(2, 15)elementos, es (ToList: 700ms, ToArray: 900ms). Agregar un elemento más lo convierte en (ToList: 925, ToArray: 1350). Me pregunto si ToArraytodavía está copiando la matriz, incluso cuando ya tiene el tamaño perfecto. Probablemente pensaron que era un hecho bastante raro que no valía la pena el condicional adicional.
StriplingWarrior
No se copió en la coincidencia de tamaño exacto, incluso antes de que comenzáramos a optimizarlo en corefx, por lo que es el caso donde obtiene la mayor cantidad de interrupciones.
Jon Hanna
12

Debe basar su decisión de elegir ToListo ToArraybasarse en cuál es la opción de diseño ideal. Si desea una colección que solo se puede iterar y acceder por índice, elija ToArray. Si desea capacidades adicionales de agregar y eliminar de la colección más adelante sin mucha molestia, entonces haga un ToList(no es realmente que no pueda agregar a una matriz, pero esa no es la herramienta adecuada para ello).

Si el rendimiento es importante, también debe considerar en qué sería más rápido operar. Siendo realistas, no llamarás ToListni ToArrayun millón de veces, pero podrías trabajar en la colección obtenida un millón de veces. En ese sentido []es mejor, ya que List<>es []con algo de sobrecarga. Vea este hilo para una comparación de eficiencia: ¿Cuál es más eficiente: List <int> o int []

En mis propias pruebas hace un tiempo, había encontrado ToArraymás rápido. Y no estoy seguro de lo sesgadas que fueron las pruebas. Sin embargo, la diferencia de rendimiento es tan insignificante, que puede notarse solo si está ejecutando estas consultas en un bucle millones de veces.

nawfal
fuente
2
Sí, si el compilador sabe que está iterando sobre una matriz (en lugar de un IEnumerable <>), puede optimizar la iteración significativamente.
RobSiklos
12

Una respuesta muy tardía pero creo que será útil para los googlers.

Ambos apestan cuando crearon usando linq. Ambos implementan el mismo código para cambiar el tamaño del búfer si es necesario . ToArrayutiliza internamente una clase para convertir IEnumerable<>a matriz, asignando una matriz de 4 elementos. Si eso no es suficiente, duplica el tamaño creando una nueva matriz que duplica el tamaño de la actual y copiando la matriz actual. Al final, asigna una nueva matriz de recuento de sus artículos. Si su consulta devuelve 129 elementos, ToArray realizará 6 asignaciones y operaciones de copia de memoria para crear una matriz de 256 elementos y luego otra matriz de 129 para devolver. tanto para la eficiencia de la memoria.

ToList hace lo mismo, pero omite la última asignación, ya que puede agregar elementos en el futuro. La lista no le importa si se crea a partir de una consulta linq o si se crea manualmente.

para la creación La lista es mejor con la memoria pero peor con la CPU, ya que la lista es una solución genérica, cada acción requiere verificaciones de rango adicionales a las verificaciones de rango internas de .net para matrices.

Entonces, si iterará a través de su conjunto de resultados demasiadas veces, entonces las matrices son buenas ya que significa menos comprobaciones de rango que las listas, y los compiladores generalmente optimizan las matrices para el acceso secuencial.

La asignación de inicialización de la lista puede ser mejor si especifica el parámetro de capacidad cuando lo crea. En este caso, asignará la matriz solo una vez, suponiendo que conozca el tamaño del resultado. ToListof linq no especifica una sobrecarga para proporcionarla, por lo que tenemos que crear nuestro método de extensión que crea una lista con la capacidad dada y luego la usa List<>.AddRange.

Para terminar esta respuesta, tengo que escribir las siguientes oraciones

  1. Al final, puede usar ToArray o ToList, el rendimiento no será tan diferente (consulte la respuesta de @EMP).
  2. Estás usando C #. Si necesita rendimiento, no se preocupe por escribir sobre el código de alto rendimiento, sino por no escribir un código de mal rendimiento.
  3. Siempre apunte a x64 para obtener un código de alto rendimiento. AFAIK, x64 JIT se basa en el compilador de C ++ y hace algunas cosas divertidas, como las optimizaciones de recursión de cola.
  4. Con 4.5 también puede disfrutar de la optimización guiada por perfil y el JIT multinúcleo.
  5. Por fin, puede usar el patrón asíncrono / espera para procesarlo más rápido.
Erdogan Kurtur
fuente
¿Ambos apestan? ¿Tiene una idea alternativa que no requiere asignación de memoria redundante?
nawfal
En el contexto de la pregunta, sí, ambos apestan, pero debido a asignaciones redundantes, y nada más. Para reducir la asignación redundante, se pueden usar listas vinculadas a expensas de la memoria y la velocidad de iteración. Al final del día, esto es lo que hacemos, hacemos compensaciones. Otra idea es crear una lista con una capacidad de 200 (por ejemplo) y luego cargar elementos. Esto también reducirá la redundancia, pero las matrices siempre son más rápidas, por lo que esta es otra compensación.
Erdogan Kurtur
¿Crear una lista de 200 ? Eso podría evitar cambiar el tamaño, pero estaba hablando de la memoria redundante utilizada. No puede evitarlo porque no hay un conocimiento previo sobre cuál podría ser el tamaño. Ya puede especificar la capacidad en el constructor de a List<T>, pero cuando no lo hace o cuando no puede, no puede evitarlo.
nawfal
2
Los únicos datos redundantes en la memoria son los contenidos de la matriz, que es una lista de punteros (en este caso). un millón de punteros de 64 bits ocupa hasta 8 MB de memoria, que no es nada en comparación con el millón de objetos a los que apuntan. 200 es solo un número, y tiene la posibilidad de reducir el número de llamadas de cambio de tamaño un máximo de 5 veces. Y sí, no podemos evitarlo. No tenemos mejores opciones. No tengo una solución mejor, pero esto no significa que no pueda decir dónde está el problema.
Erdogan Kurtur
1
hmm al final es donde dibujas la línea. Me gusta la implementación actual. El tono de su respuesta me hizo pensar que era una crítica en lugar de dónde está el problema :)
nawfal
7

Esta es una vieja pregunta, pero para el beneficio de los usuarios que se topan con ella, también existe una alternativa de 'Memorizar' lo Enumerable, que tiene el efecto de almacenar en caché y detener la enumeración múltiple de una declaración de Linq, que es lo que ToArray () y ToList () se usan mucho, aunque los atributos de colección de la lista o matriz nunca se usan.

Memoize está disponible en RX / System.Interactive lib, y se explica aquí: Más LINQ con System.Interactive

(Del blog de Bart De'Smet, que es una lectura muy recomendable si trabajas mucho con Linq to Objects)

Frep D-Oronge
fuente
4

Una opción es agregar su propio método de extensión que devuelve un solo lectura ICollection<T> . Esto puede ser mejor que usar ToListo ToArraycuando no desea usar las propiedades de indexación de una matriz / lista, o agregar / eliminar de una lista.

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();
    }
}

Pruebas unitarias:

[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);
    }
}
Weston
fuente
Vale la pena señalar que el contrato de colección de solo lectura solo estipula que el usuario del objeto no puede modificarlo, pero el propietario aún puede hacerlo si mantiene una referencia a él que ofrece una interfaz mutable. Para las interfaces que garantizan que la estructura subyacente nunca cambiará, observe las colecciones inmutables. En cuanto a por qué las colecciones inmutables, de solo lectura o de lectura y escritura simples son mejores o peores, se necesita un punto de referencia para la comparación; no hay una respuesta definitiva (de lo contrario no tendríamos que elegir).
TNE
@tne Tenga en cuenta que hago Tolist antes de AsReadOnly, por lo que no hay referencias al mutable subyacente.
Weston
Tiene toda la razón, y esa fue probablemente la mejor manera de hacer las cosas antes de que las colecciones inmutables llegaran al BCL (veo que la primera versión beta salió un mes después de su respuesta).
TNE
Existen colecciones inmutables para la seguridad del hilo, donde los hilos pueden suponer que no cambiará, y si lo hace, se crea una nueva versión, en lugar de competir contra los lectores y cambiarlo mientras lo usan. De esta manera, nadie necesita adquirir un candado.
doug65536
4

ToListAsync<T>() se prefiere.

En Entity Framework 6 ambos métodos eventualmente llaman al mismo método interno, pero ToArrayAsync<T>()llaman list.ToArray()al final, que se implementa como

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

Por lo tanto, ToArrayAsync<T>()tiene algunos gastos generales, por ToListAsync<T>()lo tanto, se prefiere.

Stephen Zeng
fuente
1
Esa es en realidad la respuesta que estaba buscando, cómo lo hace EF. Me gustaría saber cómo se trata en EF Core.
Shimmy Weitzhandler
3

Antigua pregunta pero nuevos interrogadores en todo momento.

Según la fuente de System.Linq.Enumerable , ToListsolo devuelva a new List(source), mientras ToArrayusa a new Buffer<T>(source).ToArray()para devolver a T[].

Sobre la asignación de memoria:

Mientras se ejecuta en un IEnumerable<T>único objeto, ToArrayasigne memoria una vez más que ToList. Pero no tiene que preocuparse por eso en la mayoría de los casos, porque GC hará la recolección de basura cuando sea necesario.

Sobre el tiempo de ejecución eficiente:

Quienes cuestionen esta pregunta pueden ejecutar el siguiente código en su propia máquina, y obtendrán su respuesta.

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);
}

Obtuve estos resultados en mi máquina:

Grupo 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]

Grupo 2:

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

Grupo3:

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

Grupo 4:

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]

Grupo5:

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]

Debido al límite de stackoverflow a la cantidad de caracteres de la respuesta, se omiten las listas de muestra de Group2 y Group3.

Como puede ver, realmente no es importante usarlo ToListo ToArryen la mayoría de los casos.

Al procesar IEnumerable<T>objetos calculados en tiempo de ejecución , si la carga generada por el cálculo es mayor que la asignación de memoria y las operaciones de copia de ToListy ToArray, la disparidad es insignificante ( C.ToList vs C.ToArrayy S.ToList vs S.ToArray).

La diferencia se puede observar solo en IEnumerable<T>objetos no calculados en tiempo de ejecución ( C1.ToList vs C1.ToArrayy S1.ToList vs S1.ToArray). Pero la diferencia absoluta (<60 ms) todavía es aceptable en un millón de objetos pequeños IEnumerable<T>. De hecho, la diferencia se decide mediante la implementación Enumerator<T>de IEnumerable<T>. Por lo tanto, si su programa es realmente muy sensible en esto, ¡tiene que hacer un perfil, un perfil, un perfil ! Por último, probablemente encontrará que el cuello de botella no está activado ToListo ToArray, sino el detalle de los enumeradores.

Y, el resultado C2.ToList vs C2.ToArrayy S2.ToList vs S2.ToArraymuestra que, realmente no necesita preocuparse ToListo ToArrayen ICollection<T>objetos no calculados en tiempo de ejecución .

Por supuesto, estos son solo resultados en mi máquina, el tiempo real de estas operaciones en diferentes máquinas no será el mismo, puede averiguarlo en su máquina usando el código anterior.

La única razón por la que debe elegir es que tiene necesidades específicas List<T>o T[], como se describe en la respuesta de @Jeppe Stig Nielsen .

qaqz111
fuente
1

Para cualquier persona interesada en usar este resultado en otro Linq-to-sql como

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

entonces el SQL que se genera es el mismo si usó una Lista o una Matriz para myListOrArray. Ahora sé que algunos pueden preguntar por qué incluso enumerar antes de esta declaración, pero hay una diferencia entre el SQL generado a partir de un IQueryable vs (List o Array).

Gary
fuente