Diferencias de rendimiento entre List, matrices y Array Class en C#
Lo más habitual cuando desarrollamos aplicaciones en C# y queremos introducir un número determinado de elementos dentro de colección, es tender al uso de una colección de tipo System.Collections.Generic, y concretamente al uso de List<T>.
List<T> nos proporciona una flexibilidad bastante grande y simplifica mucho el trabajo a la hora de introducir en una colección elementos.
En algunas ocasiones no tendremos claro el número de elementos que podemos introducir, pero en otras ocasiones, sabremos de antemano, cuantos elementos en conjunto (el número exacto de elementos) introduciremos dentro de la colección.
Desde el punto de vista del programador, la tendencia a usar List<T> no tiene porqué ser la más adecuada, es más, puede que sea la más nociva, sobre todo cuando hablamos de rendimiento.
Si queremos trabajar con un conjunto de elementos, podremos utilizar por ejemplo:
- List<T>
- array[]
- Array Class
De cara al rendimiento y para ver qué pasa o qué resultados podemos obtener cuando sabemos el número de elementos que tendrá nuestra colección al usar cualquiera de estas alternativas, voy a preparar un sencillo ejemplo de código.
En primer lugar, una sencilla clase que cargará dentro de un bucle, un amplio conjunto de registros.
Mediremos el momento en el que se lanza el proceso, y el momento en el que se nos indica que el proceso ha terminado, y de ahí extraeremos los tiempos que nos permitirán analizar el comportamiento.
El código de la clase que usaré para hacer esta medición es el siguiente:
using System; using System.Collections.Generic; public class ListArrayTest { public void ListInt(int iterations) { List<int> li = new List<int>(); for (int i = 0; i < iterations; i++) { li.Add(i); } } public void ListObject(int iterations) { List<object> li = new List<object>(); for (int i = 0; i < iterations; i++) { li.Add(i); } } public void ArrayInt(int iterations) { int[] a = new int[iterations]; for (int i = 0; i < iterations; i++) { a[i] = i; } } public void ArrayObject(int iterations) { object[] a = new object[iterations]; for (int i = 0; i < iterations; i++) { a[i] = i; } } public void ArrayClassInt(int iterations) { Array li = Array.CreateInstance(typeof(int), iterations); for (int i = 0; i < iterations; i++) { li.SetValue(i, i); } } public void ArrayClassObject(int iterations) { Array li = Array.CreateInstance(typeof(object), iterations); for (int i = 0; i < iterations; i++) { li.SetValue(i, i); } } }
Este ejemplo, hará 6 pruebas:
- List<int>
- List<object>
- Array<int>
- Array<object>
- Array Class int
- Array Class object
El código C# de nuestro ejemplo de aplicación de consola que se encarga de ejecutar y medir los métodos explicados en la clase anterior queda de la siguiente forma:
using System; using System.Diagnostics; public class Program { public static void Main(string[] args) { ListArrayTest listArrayTest = new ListArrayTest(); Stopwatch stopWatch = new Stopwatch(); int iterations = 10000; stopWatch.Start(); listArrayTest.ListInt(iterations); stopWatch.Stop(); Console.WriteLine("List<int> => " + stopWatch.ElapsedTicks); stopWatch.Reset(); stopWatch.Start(); listArrayTest.ListObject(iterations); stopWatch.Stop(); Console.WriteLine("List<object> => " + stopWatch.ElapsedTicks); stopWatch.Reset(); stopWatch.Start(); listArrayTest.ArrayInt(iterations); stopWatch.Stop(); Console.WriteLine("Array<int> => " + stopWatch.ElapsedTicks); stopWatch.Reset(); stopWatch.Start(); listArrayTest.ArrayObject(iterations); stopWatch.Stop(); Console.WriteLine("Array<object> => " + stopWatch.ElapsedTicks); stopWatch.Reset(); stopWatch.Start(); listArrayTest.ArrayClassInt(iterations); stopWatch.Stop(); Console.WriteLine("ArrayClass int => " + stopWatch.ElapsedTicks); stopWatch.Reset(); stopWatch.Start(); listArrayTest.ArrayClassObject(iterations); stopWatch.Stop(); Console.WriteLine("ArrayClass object => " + stopWatch.ElapsedTicks); stopWatch.Reset(); Console.ReadKey(); } }
Con 10.000 iteraciones, seremos capaces de tener una idea clara del rendimiento en el uso de una alternativa u otra.
En la siguiente captura, vemos nuestra aplicación en ejecución:
Como podemos apreciar, declarar y añadir elementos a una colección o matriz con un tipo de datos concreto, es más eficiente que hacerlo a una colección o matriz con un tipo de datos genérico como lo es object.
De igual forma, declarar y añadir elementos a una matriz de tipo int[] es más eficiente que hacerlo a una de tipo List<int>, y más aún que a una de tipo System.Array.
List es peor que array, porque almacena el valor como object, y luego convierte el tipo valor a un tipo valor por referencia.
System.Array por su parte, penaliza aún más esta situación.
En términos generales, una matriz o array es recomendable cuando sabemos el número fijo de elementos que va a haber en la colección o el número máximo de elementos posibles. No es recomendable sin embargo, cuando el número de elementos máximo es desconocido o simplemente no lo sabemos, pudiendo incrementarse o no según la ejecución de los procesos, ya que al cambiar su dimensión, copiaremos cada elemento en el nuevo array.
List<T> nos proporciona ventajas a la hora de acceder a los elementos de una colección, y aunque es menos práctica de cara al rendimiento cuando tenemos claro el número máximo de elementos, podría seguir siendo interesante su uso en esas circunstancias si las operaciones que tenemos que hacer con los elementos de la colección son costosas. No obstante, en el caso de desconocer el número máximo o fijo de elementos, es mejor que array.
No podemos por lo tanto, afirmar que algo es mejor que otra cosa.
Qué utilizar y cuando, depende de nuestras necesidades.
¡Happy Coding!