sql >> Base de Datos >  >> RDS >> Database

Foreach o For – Esa es la cuestión

La discusión sobre la diferencia de preferencia entre FOREACH y FOR no es nueva. Todos sabemos que FOREACH es más lento, pero no todos saben por qué.

Cuando comencé a aprender .NET, una persona me dijo que FOREACH es dos veces más lento que FOR. Dijo esto sin ningún motivo. Lo di por sentado.

Eventualmente, decidí explorar la diferencia de rendimiento de los bucles FOREACH y FOR, y escribir este artículo para discutir los matices.
Echemos un vistazo al siguiente código:

foreach (var item in Enumerable.Range(0, 128))
{
  Console.WriteLine(item);
}

El FOREACH es un azúcar de sintaxis. En este caso particular, el compilador lo transforma en el siguiente código:

IEnumerator<int> enumerator = Enumerable.Range(0, 128).GetEnumerator();
try
 {
   while (enumerator.MoveNext())
   {
     int item = enumerator.Current;
     Console.WriteLine(item);
   }
 }
finally
 {
  if (enumerator != null)
  {
   enumerator.Dispose();
  }
}

Sabiendo esto, podemos asumir la razón por la cual FOREACH es más lento que FOR:

  • Se está creando un nuevo objeto. Se llama Creador.
  • El método MoveNext se llama en cada iteración.
  • Cada iteración accede a la propiedad Actual.

¡Eso es todo! Sin embargo, no todo es tan fácil como parece.

Afortunadamente (o desafortunadamente), C#/CLR puede realizar optimizaciones en tiempo de ejecución. La ventaja es que el código funciona más rápido. Los estafadores:los desarrolladores deben conocer estas optimizaciones.

La matriz es un tipo profundamente integrado en CLR, y CLR proporciona una serie de optimizaciones para este tipo. El bucle FOREACH es una entidad iterable, que es un aspecto clave del rendimiento. Más adelante en el artículo, discutiremos cómo iterar a través de arreglos y listas con la ayuda del método estático Array.ForEach y el método List.ForEach.

Métodos de prueba

static double ArrayForWithoutOptimization(int[] array)
{
   int sum = 0;
   var watch = Stopwatch.StartNew();
   for (int i = 0; i < array.Length; i++)
     sum += array[i];
    watch.Stop();
    return watch.Elapsed.TotalMilliseconds;
}

static double ArrayForWithOptimization(int[] array)
{
   int length = array.Length;
   int sum = 0;
   var watch = Stopwatch.StartNew();
    for (int i = 0; i < length; i++)
      sum += array[i];
    watch.Stop();
     return watch.Elapsed.TotalMilliseconds;
}

static double ArrayForeach(int[] array)
{
  int sum = 0;
  var watch = Stopwatch.StartNew();
   foreach (var item in array)
    sum += item;
  watch.Stop();
  return watch.Elapsed.TotalMilliseconds;
}

static double ArrayForEach(int[] array)
{
  int sum = 0;
  var watch = Stopwatch.StartNew();
  Array.ForEach(array, i => { sum += i; });
  watch.Stop();
  return watch.Elapsed.TotalMilliseconds;
}

Condiciones de prueba:

  • La opción "Optimizar código" está activada.
  • El número de elementos es igual a 100 000 000 (tanto en la matriz como en la lista).
  • Especificación de PC:Intel Core i-5 y 8 GB de RAM.

Arreglos

El diagrama muestra que FOR y FOREACH pasan la misma cantidad de tiempo mientras iteran a través de matrices. Y es porque la optimización CLR convierte FOREACH en FOR y usa la longitud de la matriz como el límite máximo de iteración. No importa si la longitud de la matriz se almacena en caché o no (cuando se usa FOR), el resultado es casi el mismo.

Puede sonar extraño, pero el almacenamiento en caché de la longitud de la matriz puede afectar el rendimiento. Mientras usa matriz .Length como límite de iteración, JIT prueba el índice para llegar al borde derecho más allá del ciclo. Esta comprobación se realiza una sola vez.
Es muy fácil destruir esta optimización. El caso en que la variable se almacena en caché apenas está optimizado.

Array.foreach mostró los peores resultados. Su implementación es bastante simple:

public static void ForEach<T>(T[] array, Action<T> action)
 {
  for (int index = 0; index < array.Length; ++index)
    action(array[index]);
 }

Entonces, ¿por qué va tan lento? Utiliza FOR debajo del capó. Bueno, la razón está en llamar al delegado de ACCIÓN. De hecho, se llama a un método en cada iteración, lo que reduce el rendimiento. Además, los delegados no se invocan tan rápido como nos gustaría.

Listas

El resultado es completamente diferente. Al iterar listas, FOR y FOREACH muestran resultados diferentes. No hay optimización. FOR (con el almacenamiento en caché de la longitud de la lista) muestra el mejor resultado, mientras que FOREACH es más de 2 veces más lento. Es porque se ocupa de MoveNext y Current bajo el capó. List.ForEach y Array.ForEach muestran el peor resultado. Los delegados siempre son llamados virtualmente. La implementación de este método se ve así:

public void ForEach(Action<T> action)
{
  int num = this._version;
   for (int index = 0; index < this._size && num == this._version; ++index)
     action(this._items[index]);
   if (num == this._version)
     return;
   ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}

Cada iteración llama al delegado de Acción. También verifica si la lista ha cambiado y, de ser así, se lanza una excepción.

List usa internamente un modelo basado en arreglos y el método ForEach usa el índice del arreglo para iterar, lo cual es significativamente más rápido que usar el indexador.

Números específicos

  1. El bucle FOR sin almacenamiento en caché de longitud y FOREACH funcionan un poco más rápido en matrices que FOR con almacenamiento en caché de longitud.
  2. Array.Foreach rendimiento es aproximadamente 6 veces más lento que el rendimiento de FOR / FOREACH.
  3. El bucle FOR sin almacenamiento en caché de longitud funciona 3 veces más lento en las listas, en comparación con las matrices.
  4. El bucle FOR con almacenamiento en caché de longitud funciona 2 veces más lento en las listas, en comparación con las matrices.
  5. El ciclo FOREACH funciona 6 veces más lento en las listas, en comparación con las matrices.

Aquí hay una tabla de clasificación para listas:

Y para arreglos:

Conclusión

Realmente disfruté esta investigación, especialmente el proceso de escritura, y espero que ustedes también la hayan disfrutado. Al final resultó que, FOREACH es más rápido en matrices que FOR con persecución de longitud. En las estructuras de listas, FOREACH es más lento que FOR.

El código se ve mejor cuando se usa FOREACH y los procesadores modernos permiten usarlo. Sin embargo, si necesita optimizar mucho su base de código, es mejor usar FOR.

¿Qué piensas, qué bucle se ejecuta más rápido, FOR o FOREACH?