Database
 sql >> Database >  >> RDS >> Database

Foreach o For:questa è la domanda

La discussione sulla differenza di preferenza tra FOREACH e FOR non è nuova. Sappiamo tutti che FOREACH è più lento, ma non tutti sappiamo perché.

Quando ho iniziato a imparare .NET, una persona mi ha detto che FOREACH è due volte più lento di FOR. Lo ha detto senza alcun motivo. Lo davo per scontato.

Alla fine, ho deciso di esplorare la differenza di prestazioni del ciclo FOREACH e FOR e scrivere questo articolo per discutere le sfumature.
Diamo un'occhiata al seguente codice:

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

Il FOREACH è uno zucchero sintattico. In questo caso particolare, il compilatore lo trasforma nel codice seguente:

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();
  }
}

Sapendo questo, possiamo supporre il motivo per cui FOREACH è più lento di FOR:

  • È in corso la creazione di un nuovo oggetto. Si chiama Creatore.
  • Il metodo MoveNext viene chiamato ad ogni iterazione.
  • Ogni iterazione accede alla proprietà Current.

Questo è tutto! Tuttavia, non è tutto così facile come sembra.

Fortunatamente (o sfortunatamente), C#/CLR può eseguire ottimizzazioni in fase di esecuzione. Il vantaggio è che il codice funziona più velocemente. Il contro:gli sviluppatori dovrebbero essere consapevoli di queste ottimizzazioni.

L'array è un tipo profondamente integrato in CLR e CLR fornisce una serie di ottimizzazioni per questo tipo. Il ciclo FOREACH è un'entità iterabile, che è un aspetto chiave della performance. Più avanti nell'articolo, discuteremo come scorrere tra array ed elenchi con l'aiuto del metodo statico Array.ForEach e del metodo List.ForEach.

Metodi di prova

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;
}

Condizioni di prova:

  • L'opzione "Ottimizza codice" è attivata.
  • Il numero di elementi è pari a 100 000 000 (sia nell'array che nell'elenco).
  • Specifiche del PC:Intel Core i-5 e 8 GB di RAM.

Array

Il diagramma mostra che FOR e FOREACH impiegano la stessa quantità di tempo durante l'iterazione degli array. Ed è perché l'ottimizzazione CLR converte FOREACH in FOR e utilizza la lunghezza dell'array come limite massimo di iterazione. Non importa se la lunghezza dell'array è memorizzata nella cache o meno (quando si utilizza FOR), il risultato è quasi lo stesso.

Può sembrare strano, ma la memorizzazione nella cache della lunghezza dell'array può influire sulle prestazioni. Durante l'utilizzo di array .Length come limite dell'iterazione, JIT verifica l'indice per raggiungere il bordo destro oltre il ciclo. Questo controllo viene eseguito una sola volta.
È molto facile distruggere questa ottimizzazione. Il caso in cui la variabile è memorizzata nella cache è difficilmente ottimizzato.

Array.foreach dimostrato i risultati peggiori. La sua implementazione è abbastanza semplice:

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

Allora perché è così lento? Usa FOR sotto il cofano. Bene, il motivo sta nel chiamare il delegato ACTION. In effetti, ad ogni iterazione viene chiamato un metodo che riduce le prestazioni. Inoltre, i delegati vengono invocati non così velocemente come vorremmo.

Elenchi

Il risultato è completamente diverso. Durante l'iterazione degli elenchi, FOR e FOREACH mostrano risultati diversi. Non c'è ottimizzazione. FOR (con la memorizzazione nella cache della lunghezza dell'elenco) mostra il miglior risultato, mentre FOREACH è più di 2 volte più lento. È perché si occupa di MoveNext e Current sotto il cofano. List.ForEach e Array.ForEach mostrano il risultato peggiore. I delegati sono sempre chiamati virtualmente. L'implementazione di questo metodo si presenta così:

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);
}

Ogni iterazione chiama il delegato Action. Verifica anche se l'elenco è stato modificato e, in tal caso, viene generata un'eccezione.

List utilizza internamente un modello basato su array e il metodo ForEach utilizza l'indice di array per scorrere, il che è significativamente più veloce rispetto all'utilizzo dell'indicizzatore.

Numeri specifici

  1. Il ciclo FOR senza memorizzazione nella cache della lunghezza e FOREACH funzionano leggermente più velocemente sugli array rispetto a FOR con memorizzazione nella cache della lunghezza.
  2. Array.Foreach prestazioni è circa 6 volte più lento delle prestazioni FOR / FOREACH.
  3. Il ciclo FOR senza memorizzazione nella cache della lunghezza funziona 3 volte più lentamente sugli elenchi, rispetto agli array.
  4. Il ciclo FOR con memorizzazione nella cache della lunghezza funziona 2 volte più lentamente sugli elenchi, rispetto agli array.
  5. Il ciclo FOREACH funziona 6 volte più lentamente sugli elenchi, rispetto agli array.

Ecco una classifica per le liste:

E per gli array:

Conclusione

Mi è davvero piaciuta questa indagine, in particolare il processo di scrittura, e spero che anche a voi sia piaciuto. Come si è scoperto, FOREACH è più veloce sugli array rispetto a FOR con la ricerca della lunghezza. Nelle strutture degli elenchi, FOREACH è più lento di FOR.

Il codice ha un aspetto migliore quando si utilizza FOREACH e i moderni processori ne consentono l'utilizzo. Tuttavia, se hai bisogno di ottimizzare notevolmente la tua base di codice, è meglio usare FOR.

Cosa ne pensi, quale loop è più veloce, FOR o FOREACH?