¿Hay un impacto en el rendimiento al llamar a ToList ()?

Cuando se utiliza ToList() , ¿hay un impacto en el rendimiento que deba tenerse en cuenta?

Estaba escribiendo una consulta para recuperar archivos de un directorio, que es la consulta:

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

Sin embargo, dado que me gusta trabajar con List , decidí poner …

List imageList = Directory.GetFiles(directory).ToList();

Entonces, ¿hay algún tipo de impacto en el rendimiento que deba tenerse en cuenta al decidir realizar una conversión como esta, o solo para tener en cuenta cuando se trata de una gran cantidad de archivos? ¿Es esto una conversión insignificante?

IEnumerable.ToList()

Sí, IEnumerable.ToList() tiene un impacto en el rendimiento, es una operación O (n) aunque probablemente solo requiera atención en operaciones críticas de rendimiento.

La operación ToList() utilizará el List(IEnumerable collection) . Este constructor debe hacer una copia de la matriz (más generalmente IEnumerable ), de lo contrario las modificaciones futuras de la matriz original cambiarán en la fuente T[] también, lo que no sería deseable en general.

Me gustaría reiterar que esto solo marcará la diferencia con una gran lista, copiar trozos de memoria es una operación bastante rápida de realizar.

Consejo útil, As vs To

Notará que en LINQ hay varios métodos que comienzan con As (como AsEnumerable() ) y To (como ToList() ). Los métodos que comienzan con Requerir una conversión como la anterior (es decir, pueden afectar el rendimiento) y los métodos que comienzan con Lo que no y solo requerirán un lanzamiento o una operación simple.

Detalles adicionales sobre List

Aquí hay un poco más de detalles sobre cómo List funciona en caso de que esté interesado 🙂

Una List también usa una construcción llamada una matriz dinámica que necesita cambiar de tamaño bajo demanda, este evento de cambio de tamaño copia el contenido de una matriz anterior 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 y Count en la List . Capacity refiere al tamaño de la matriz detrás de las escenas. Count es la cantidad de elementos en la List que siempre es < = Capacity . Por lo tanto, cuando se agrega un elemento a la lista, al boostla después de Capacity , el tamaño de la List se duplica y la matriz se copia.

¿Hay un impacto en el rendimiento al llamar a List ()?

Sí, por supuesto. Teóricamente, incluso i++ tiene un impacto en el rendimiento, ralentiza el progtwig por unos pocos tics.

¿Qué hace .ToList ?

Cuando invoca .ToList , el código llama a Enumerable.ToList() que es un método de extensión que return new List(source) . En el constructor correspondiente, en la peor de las circunstancias, pasa por el contenedor de elementos y los agrega uno por uno a un nuevo contenedor. Entonces, su comportamiento afecta poco al rendimiento. Es imposible ser un cuello de botella de rendimiento de su aplicación.

¿Qué pasa con el código en la pregunta?

Directory.GetFiles recorre la carpeta y devuelve todos los nombres de los archivos de inmediato a la memoria, existe el riesgo potencial de que la cadena [] cueste mucha memoria, ralentizando todo.

Qué debería hacerse entonces

Depende. Si usted (así como su lógica comercial) garantiza que el monto del archivo en la carpeta siempre es pequeño, el código es aceptable. Pero aún así se sugiere usar una versión perezosa: Directory.EnumerateFiles en C # 4. Esto se parece mucho más a una consulta, que no se ejecutará de inmediato, puede agregar más consultas sobre ella, como:

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

que dejará de buscar en la ruta tan pronto como se encuentre un archivo cuyo nombre contenga “miarchivo”. Obviamente, esto tiene un mejor rendimiento que .GetFiles .

¿Hay un impacto en el rendimiento al llamar a List ()?

Sí hay. Usando el método de extensión, Enumerable.ToList() construirá un nuevo objeto List de la colección de fonts IEnumerable que, por supuesto, tiene un impacto en el rendimiento.

Sin embargo, comprender List puede ayudarlo a determinar si el impacto en el rendimiento es significativo.

List usa una matriz ( T[] ) para almacenar los elementos de la lista. Las matrices no pueden extenderse una vez que se asignan para que List use una matriz de gran tamaño para almacenar los elementos de la lista. Cuando la List crece más allá del tamaño de la matriz subyacente, se debe asignar una nueva matriz y el contenido de la matriz anterior debe copiarse a la nueva matriz más grande antes de que la lista pueda crecer.

Cuando se construye una nueva List partir de un IEnumerable hay dos casos:

  1. La colección de origen implementa ICollection : Entonces ICollection.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 copiar todos los elementos de la colección de origen a la matriz de respaldo usando ICollection.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 memoria de rendimiento se requiere para la nueva matriz y se requieren ciclos de CPU para copiar todos los elementos.

  2. De lo contrario, se desconoce el tamaño de la colección de origen y el enumerador de IEnumerable se usa para agregar cada elemento de origen de uno en uno a la nueva List . Inicialmente, la matriz de respaldo está vacía y se crea una matriz de tamaño 4. Entonces, cuando esta matriz es demasiado pequeña, el tamaño se duplica para que la matriz de respaldo crezca así 4, 8, 16, 32, etc. Cada vez que la matriz de respaldo crece debe reasignarse y todos los elementos almacenados hasta el momento deben copiarse. Esta operación es mucho más costosa en comparación con el primer caso donde se puede crear una matriz del tamaño correcto de inmediato.

    Además, si su colección de fonts contiene, por ejemplo, 33 elementos, la lista terminará usando una matriz de 64 elementos desperdiciando algo de memoria.

En su caso, la colección fuente es una matriz que implementa ICollection por lo que el impacto en el rendimiento no es algo que deba preocupar, a menos que la matriz fuente sea muy grande. Llamar a ToList() simplemente copiará el conjunto de origen y lo envolverá en un objeto List . Incluso el rendimiento del segundo caso no es algo de lo que preocuparse para pequeñas colecciones.

“¿hay algún impacto en el rendimiento que deba tenerse en cuenta?”

El problema con su escenario preciso es que, en primer lugar, su verdadera preocupación sobre el rendimiento sería la velocidad de la unidad de disco duro y la eficacia de la memoria caché de la unidad.

Desde esa perspectiva, el impacto es seguramente insignificante hasta el punto de que NO no necesita ser considerado.

PERO SÓLO si realmente necesita las características de la estructura List<> para posiblemente hacerle más productivo, o su algoritmo más amigable, o alguna otra ventaja. De lo contrario, solo está agregando deliberadamente un golpe de rendimiento insignificante, sin ningún motivo. En cuyo caso, naturalmente, ¡no deberías hacerlo! 🙂

ToList() crea una nueva lista y pone los elementos en ella, lo que significa que hay un costo asociado al hacer ToList() . En el caso de una colección pequeña, no será un costo muy notable, pero tener una gran colección puede causar un golpe de rendimiento en caso de utilizar ToList.

Por lo general, no debe usar ToList () a menos que el trabajo que está realizando no se pueda realizar sin convertir la recostackción a List. Por ejemplo, si solo desea iterar a través de la colección, no es necesario que realice ToList

Si está realizando consultas en una fuente de datos, por ejemplo, una Base de datos utilizando LINQ to SQL, el costo de ToList es mucho mayor porque utiliza ToList con LINQ to SQL en lugar de ejecutar Ejecución diferida, es decir, cargar elementos cuando es necesario (lo cual puede ser beneficioso) en muchos escenarios) carga instantáneamente elementos de la base de datos en la memoria

Será tan (como) eficiente como hacer:

 var list = new List(items); 

Si desensambla el código fuente del constructor que toma un IEnumerable , verá que hará algunas cosas:

  • Call collection.Count , por lo que si collection es un IEnumerable , forzará la ejecución. Si la collection es una matriz, lista, etc., debería ser O(1) .

  • Si collection implementa ICollection , guardará los elementos en una matriz interna utilizando el método ICollection.CopyTo . Debería ser O(n) , siendo n la longitud de la colección.

  • Si la collection no implementa ICollection , recorrerá 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, será O(n) , ya que recorrerá la collection para hacer una copia de cada elemento.

ToList creará una nueva lista y copiará los elementos de la fuente original a la lista recién creada, por lo que solo debe copiar los elementos de la fuente original y depende del tamaño de la fuente.

Teniendo en cuenta el rendimiento de la recuperación de la lista de archivos, ToList() es insignificante. Pero no realmente para otros escenarios. Eso realmente depende de dónde lo estés usando.

  • Cuando llama a una matriz, lista u otra colección, crea una copia de la colección como List . 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. Entonces, el impacto en el rendimiento depende de la cantidad de archivos.

  • Cuando llamas a un IEnumerable , materializas el IEnumerable (generalmente una consulta).