Cuando se usa ToList()
, ¿hay un impacto en el rendimiento que deba considerarse?
Estaba escribiendo una consulta para recuperar archivos de un directorio, que es la consulta:
string[] imageArray = Directory.GetFiles(directory);
Sin embargo, como me gusta trabajar con él List<>
, decidí poner ...
List<string> imageList = Directory.GetFiles(directory).ToList();
Entonces, ¿hay algún tipo de impacto en el rendimiento que deba tenerse en cuenta al decidir hacer una conversión como esta, o que solo se tenga en cuenta al tratar con una gran cantidad de archivos? ¿Es esta una conversión insignificante?
c#
arrays
performance
list
Cody
fuente
fuente
List<T>
a favor de unaT[]
si se hace que el código sea más lógico / lectura / mantenible (a menos que, por supuesto, la conversión fue causando notables problemas de rendimiento en cuyo caso me re- visitarlo, supongo).Add
oRemove
, lo dejaría comoIEnumerable<T>
(o incluso mejorvar
)EnumerateFiles
lugar deGetFiles
, por lo que solo se creará una matriz.GetFiles(directory)
, como se implementa actualmente en .NET, prácticamente lo hacenew List<string>(EnumerateFiles(directory)).ToArray()
. EntoncesGetFiles(directory).ToList()
crea una lista, crea una matriz a partir de eso, luego crea una lista nuevamente. Como dice 2kay, deberías preferir hacerEnumerateFiles(directory).ToList()
aquí.Respuestas:
IEnumerable.ToList()
Sí,
IEnumerable<T>.ToList()
tiene un impacto en el rendimiento, es una operación O (n) , aunque es probable que solo requiera atención en las operaciones críticas de rendimiento.La
ToList()
operación utilizará elList(IEnumerable<T> collection)
constructor. Este constructor debe hacer una copia de la matriz (más generalmenteIEnumerable<T>
), de lo contrario, las modificaciones futuras de la matriz original también cambiarán en la fuenteT[]
, lo que generalmente no sería deseable.Me gustaría reiterar que esto solo marcará la diferencia con una lista enorme, la copia de fragmentos de memoria es una operación bastante rápida de realizar.
Consejo práctico,
As
vsTo
Notarás que en LINQ hay varios métodos que comienzan con
As
(comoAsEnumerable()
) yTo
(comoToList()
). Los métodos que comienzan conTo
requieren una conversión como la anterior (es decir, pueden afectar el rendimiento), y los métodos que comienzan conAs
no requieren y solo requerirán un poco de conversión o una operación simple.Detalles adicionales sobre
List<T>
Aquí hay un poco más de detalles sobre cómo
List<T>
funciona en caso de que esté interesado :)A
List<T>
también utiliza una construcción llamada matriz dinámica que debe redimensionarse a pedido, este evento de cambio de tamaño copia el contenido de una matriz antigua a la nueva matriz. Por lo tanto, comienza pequeño y aumenta de tamaño si es necesario .Esta es la diferencia entre los atributos
Capacity
yCount
enList<T>
.Capacity
se refiere al tamaño de la matriz detrás de escena,Count
es el número de elementos en elList<T>
que siempre está<= Capacity
. Entonces, cuando se agrega un elemento a la lista, incrementándoloCapacity
, el tamaño del mismoList<T>
se duplica y la matriz se copia.fuente
List(IEnumerable<T> collection)
constructor verifica si el parámetro de colección esICollection<T>
y luego crea una nueva matriz interna con el tamaño requerido de inmediato. Si la colección de parámetros no lo esICollection<T>
, el constructor lo itera y llamaAdd
a cada elemento.Sí, por supuesto. Teóricamente, incluso
i++
tiene un impacto en el rendimiento, ralentiza el programa por unos pocos tics.¿Qué
.ToList
hacer?Cuando invocas
.ToList
, el código llama,Enumerable.ToList()
que es un método de extensión quereturn new List<TSource>(source)
. En el constructor correspondiente, en la peor de las circunstancias, pasa por el contenedor del elemento y los agrega uno por uno a un nuevo contenedor. Por lo tanto, su comportamiento afecta poco al rendimiento. Es imposible ser un cuello de botella de rendimiento de su aplicación.¿Qué hay de malo con el código en la pregunta?
Directory.GetFiles
pasa a través de la carpeta y devuelve todos los nombres de los archivos inmediatamente a la memoria, tiene el riesgo potencial de que la cadena [] cueste mucha memoria, ralentizando todo.¿Qué se debe hacer entonces?
Depende. Si usted (así como su lógica empresarial) garantiza que la cantidad de archivos en la carpeta siempre es pequeña, el código es aceptable. Pero aún se sugiere usar una versión perezosa:
Directory.EnumerateFiles
en C # 4. Esto es mucho más como una consulta, que no se ejecutará de inmediato, puede agregar más consultas como:que dejará de buscar la ruta tan pronto como se encuentre un archivo cuyo nombre contenga "myfile". Obviamente, esto tiene un mejor rendimiento entonces
.GetFiles
.fuente
Sí hay. El uso del método de extensión
Enumerable.ToList()
construirá un nuevoList<T>
objeto de laIEnumerable<T>
colección de origen que, por supuesto, tiene un impacto en el rendimiento.Sin embargo, la comprensión
List<T>
puede ayudarlo a determinar si el impacto en el rendimiento es significativo.List<T>
usa una matriz (T[]
) para almacenar los elementos de la lista. Las matrices no se pueden extender una vez que se asignan, porList<T>
lo que utilizarán una matriz sobredimensionada para almacenar los elementos de la lista. CuandoList<T>
crece más allá del tamaño de la matriz subyacente, debe asignarse una nueva matriz y el contenido de la matriz anterior debe copiarse en la nueva matriz más grande antes de que la lista pueda crecer.Cuando
List<T>
se construye una nueva a partir de una,IEnumerable<T>
hay dos casos:La colección de origen se implementa
ICollection<T>
: luegoICollection<T>.Count
se utiliza para obtener el tamaño exacto de la colección de origen y se asigna una matriz de respaldo coincidente antes de que todos los elementos de la colección de origen se copien en la matriz de respaldo medianteICollection<T>.CopyTo()
. Esta operación es bastante eficiente y probablemente se asignará a algunas instrucciones de la CPU para copiar bloques de memoria. Sin embargo, en términos de rendimiento, se requiere memoria para la nueva matriz y se requieren ciclos de CPU para copiar todos los elementos.De lo contrario, se desconoce el tamaño de la colección de origen y
IEnumerable<T>
se utiliza el enumerador de para agregar cada elemento de origen, uno por vez, al nuevoList<T>
. Inicialmente, la matriz de respaldo está vacía y se crea una matriz de tamaño 4. Luego, cuando esta matriz es demasiado pequeña, el tamaño se duplica, por lo que la matriz de respaldo crece de esta manera 4, 8, 16, 32, etc. Esta operación es mucho más costosa en comparación con el primer caso en el que se puede crear una matriz del tamaño correcto de inmediato.Además, si su colección de origen contiene, digamos, 33 elementos, la lista terminará usando una matriz de 64 elementos que desperdiciarán algo de memoria.
En su caso, la colección fuente es una matriz que se implementa,
ICollection<T>
por lo que el impacto en el rendimiento no es algo de lo que deba preocuparse a menos que su matriz fuente sea muy grande. Las llamadasToList()
simplemente copiarán la matriz fuente y la envolverán en unList<T>
objeto. Incluso el rendimiento del segundo caso no es algo de qué preocuparse para las colecciones pequeñas.fuente
El problema con su escenario preciso es que, ante todo, su verdadera preocupación sobre el rendimiento sería la velocidad del disco duro y la eficiencia de la memoria caché del disco.
Desde esa perspectiva, el impacto seguramente es insignificante hasta el punto de que NO no necesita ser considerado.
PERO SOLO si realmente necesita las características de la
List<>
estructura para posiblemente hacerlo más productivo, o su algoritmo más amigable, o alguna otra ventaja. De lo contrario, solo está agregando deliberadamente un éxito de rendimiento insignificante, sin ninguna razón. En cuyo caso, naturalmente, ¡no deberías hacerlo! :)fuente
ToList()
crea una nueva Lista y coloca los elementos en ella, lo que significa que hay un costo asociado con hacerloToList()
. En el caso de una colección pequeña, no será un costo muy notable, pero tener una colección enorme puede causar un impacto en el rendimiento en caso de usar ToList.En general, no debe usar ToList () a menos que el trabajo que está haciendo no se pueda hacer sin convertir la colección a List. Por ejemplo, si solo desea iterar por la colección, no necesita realizar ToList
Si está realizando consultas en una fuente de datos, por ejemplo, una base de datos que utiliza LINQ to SQL, el costo de hacer ToList es mucho mayor porque cuando usa ToList con LINQ to SQL en lugar de hacer una ejecución demorada, es decir, cargar elementos cuando sea necesario (lo que puede ser beneficioso) en muchos escenarios) carga instantáneamente elementos de la base de datos en la memoria
fuente
Será tan (in) eficiente como hacer:
Si desmonta el código fuente del constructor que toma un
IEnumerable<T>
, verá que hará algunas cosas:Llame
collection.Count
, así que sicollection
es unIEnumerable<T>
, forzará la ejecución. Sicollection
es una matriz, lista, etc., debería serO(1)
.Si se
collection
implementaICollection<T>
, guardará los elementos en una matriz interna utilizando elICollection<T>.CopyTo
método Se debe serO(n)
, siendon
la longitud de la colección.Si
collection
no se implementaICollection<T>
, iterará a través de los elementos de la colección y los agregará a una lista interna.Entonces, sí, consumirá más memoria, ya que tiene que crear una nueva lista, y en el peor de los casos, lo será
O(n)
, ya que iterará a través decollection
para hacer una copia de cada elemento.fuente
0(n)
donden
está la suma total de bytes que ocupan las cadenas de la colección original, no el recuento de los elementos (para ser más exactos n = bytes / tamaño de palabra)bool
,int
, Etc.)? Realmente no tiene que hacer una copia de cada cadena de la colección. Simplemente agréguelos a la nueva lista.Teniendo en cuenta el rendimiento de la recuperación de la lista de archivos,
ToList()
es insignificante. Pero en realidad no para otros escenarios. Eso realmente depende de dónde lo estés usando.Al invocar una matriz, lista u otra colección, crea una copia de la colección como a
List<T>
. El rendimiento aquí depende del tamaño de la lista. Deberías hacerlo cuando sea realmente necesario.En su ejemplo, lo llama en una matriz. Se itera sobre la matriz y agrega los elementos uno por uno a una lista recién creada. Por lo tanto, el impacto en el rendimiento depende de la cantidad de archivos.
Al llamar a un
IEnumerable<T>
, materializa elIEnumerable<T>
(generalmente una consulta).fuente
ToList creará una nueva lista y copiará elementos de la fuente original a la lista recién creada, por lo que lo único es copiar los elementos de la fuente original y depende del tamaño de la fuente
fuente