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?
.net
linq
performance
Frank Krueger
fuente
fuente
Respuestas:
A menos que simplemente necesite una matriz para cumplir con otras restricciones que debe usar
ToList
. En la mayoría de los escenariosToArray
se asignará más memoria queToList
.Ambos usan matrices para el almacenamiento, pero
ToList
tienen 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,ToArray
necesita que la matriz tenga un tamaño exactamente igual al número de elementos.Para cumplir con esta restricción, a
ToArray
menudo se hace una asignación más queToList
. 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
ToArray
antemano.En general, creo que este es el caso más raro. Es mucho más común ver muchas
ToArray
llamadas que se pasan inmediatamente a otros usos de memoria de corta duración, en cuyo casoToList
es demostrablemente mejor.La clave aquí es perfilar, perfilar y luego perfilar un poco más.
fuente
ToArray
puede asignar más memoria si necesita el tamaño exacto de las ubicaciones dondeToList<>
obviamente tiene sus ubicaciones de repuesto automáticas. (autoincrease)La diferencia de rendimiento será insignificante, ya que
List<T>
se implementa como una matriz de tamaño dinámico. Llamar ya seaToArray()
(que usa unaBuffer<T>
clase interna para hacer crecer la matriz) oToList()
(que llama alList<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.
fuente
ToArray()
yToList()
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 grandestruct
. Solo comida para pensar.ToList
oToArray
comenzará 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%.List
yBuffer
verificaránICollection
, en cuyo caso el rendimiento será idéntico.(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 aList<T>
.Mejor ilustrado con el ejemplo:
El código anterior se ejecutará sin excepción y produce el resultado:
Esto muestra que el
IEnumarator<int>
devuelto por unint[]
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
source
como unIList<int>
. De esa manera me aseguro de que el compilador de C # no optimice laforeach
declaración en algo que sea equivalente a unfor (var idx = 0; idx < source.Length; idx++) { /* ... */ }
bucle. Esto es algo que el compilador de C # podría hacer si lo usovar 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:seguido de una
System.InvalidOperationException
explosión diciendo: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 unIEnumerator<int>
cuadro en este caso porque lo usoIList<int>
).En conclusión, el enumerador producido por un
List<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_version
en esta clase que se cambia cada vez que elList<>
se actualiza.)fuente
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.
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.fuente
struct
usaras un tipo o clase grande en lugar de uno primitivo.ToList
oToArray
no la enumeración de ningunoIEnumerable
. List <T> .ToList () todavía crea una nueva List <T>, no simplemente "devuelve esto".ToArray()
yToList()
difieren demasiado cuando se les proporciona unICollection<T>
parámetro: solo realizan una única asignación y una sola operación de copia. AmbosList<T>
eArray
implementarICollection<T>
, por lo que sus puntos de referencia no son válidos en absoluto..Select(i => i)
para evitar elICollection<T>
problema de implementación, e incluye un grupo de control para ver cuánto tiempo se pasa iterando sobre la fuenteIEnumerable<>
en primer lugar.ToList()
generalmente se prefiere si lo usa enIEnumerable<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.fuente
Enumerable.ToArray()
llamadasnew 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.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.
fuente
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.
Los resultados son:
fuente
ToImmutableArray()
(del Sistema.Colecciones.Paquete inmutable) 😉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.Enumerable
espacio de nombres de extensiones, y he notado una optimización muy común.Siempre que sea posible, la
IEnumerable<T>
fuente se convierteIList<T>
uICollection<T>
optimiza el método. Por ejemplo, miraElementAt(int)
.Curiosamente, Microsoft eligió optimizar solo para
IList<T>
, pero noIList
. Parece que Microsoft prefiere usar laIList<T>
interfaz.System.Array
solo implementaIList
, 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>
.fuente
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.
Puede descargar la secuencia de comandos LINQPad aquí .
Resultados:
Al ajustar el código anterior, descubrirá que:
int
s en lugar destring
s.struct
s grande en lugar destring
s 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:
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
Select
que es posible que unaToList()
oToArray()
aplicación para predecir el tamaño por delante de la colección resultante de tiempo. Reemplazar.Select(i => i)
el código anterior conWhere(i => true)
resultados muy similares en este momento, y es más probable que lo haga independientemente de la implementación de .NET.fuente
100000
y lo usará para optimizar ambosToList()
yToArray()
, alToArray()
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 lugarToList()
tiene la ventaja. El ejemplo en la pregunta aún se perdería, porqueWhere
no se puede hacer la predicción de tamaño..Select(i => i)
podría ser reemplazado con.Where(i => true)
para corregir eso.ToArray()
una ventaja) y uno que no lo es, como se indicó anteriormente, y comparar los resultados.ToArray()
todavía pierde en el mejor de los casos. ConMath.Pow(2, 15)
elementos, es (ToList: 700ms, ToArray: 900ms). Agregar un elemento más lo convierte en (ToList: 925, ToArray: 1350). Me pregunto siToArray
todaví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.Debe basar su decisión de elegir
ToList
oToArray
basarse 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, elijaToArray
. Si desea capacidades adicionales de agregar y eliminar de la colección más adelante sin mucha molestia, entonces haga unToList
(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
ToList
niToArray
un millón de veces, pero podrías trabajar en la colección obtenida un millón de veces. En ese sentido[]
es mejor, ya queList<>
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
ToArray
má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.fuente
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 .
ToArray
utiliza internamente una clase para convertirIEnumerable<>
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.
ToList
of 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 usaList<>.AddRange
.Para terminar esta respuesta, tengo que escribir las siguientes oraciones
fuente
List<T>
, pero cuando no lo hace o cuando no puede, no puede evitarlo.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)
fuente
Una opción es agregar su propio método de extensión que devuelve un solo lectura
ICollection<T>
. Esto puede ser mejor que usarToList
oToArray
cuando no desea usar las propiedades de indexación de una matriz / lista, o agregar / eliminar de una lista.Pruebas unitarias:
fuente
ToListAsync<T>()
se prefiere.En Entity Framework 6 ambos métodos eventualmente llaman al mismo método interno, pero
ToArrayAsync<T>()
llamanlist.ToArray()
al final, que se implementa comoPor lo tanto,
ToArrayAsync<T>()
tiene algunos gastos generales, porToListAsync<T>()
lo tanto, se prefiere.fuente
Antigua pregunta pero nuevos interrogadores en todo momento.
Según la fuente de System.Linq.Enumerable ,
ToList
solo devuelva anew List(source)
, mientrasToArray
usa anew Buffer<T>(source).ToArray()
para devolver aT[]
.Mientras se ejecuta en un
IEnumerable<T>
único objeto,ToArray
asigne memoria una vez más queToList
. 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.Quienes cuestionen esta pregunta pueden ejecutar el siguiente código en su propia máquina, y obtendrán su respuesta.
Obtuve estos resultados en mi máquina:
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
ToList
oToArry
en 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 deToList
yToArray
, la disparidad es insignificante (C.ToList vs C.ToArray
yS.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.ToArray
yS1.ToList vs S1.ToArray
). Pero la diferencia absoluta (<60 ms) todavía es aceptable en un millón de objetos pequeñosIEnumerable<T>
. De hecho, la diferencia se decide mediante la implementaciónEnumerator<T>
deIEnumerable<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á activadoToList
oToArray
, sino el detalle de los enumeradores.Y, el resultado
C2.ToList vs C2.ToArray
yS2.ToList vs S2.ToArray
muestra que, realmente no necesita preocuparseToList
oToArray
enICollection<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>
oT[]
, como se describe en la respuesta de @Jeppe Stig Nielsen .fuente
Para cualquier persona interesada en usar este resultado en otro Linq-to-sql como
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).
fuente