In .NET, quale loop gira più velocemente, ‘for’ o ‘foreach’?

In C # / VB.NET / .NET, quale loop gira più veloce, for o foreach ?

Da quando ho letto che un ciclo for funziona più velocemente di un ciclo foreach molto tempo fa, ho pensato che fosse vero per tutte le collezioni, collezioni generiche, tutti gli array, ecc.

Ho setacciato Google e ho trovato alcuni articoli, ma la maggior parte di questi sono inconcludenti (leggi i commenti sugli articoli) e open ended.

Quale sarebbe l’ideale è avere ogni scenario elencato e la soluzione migliore per lo stesso.

Ad esempio (solo un esempio di come dovrebbe essere):

  1. per iterare una serie di oltre 1000 stringhe – for è meglio di foreach
  2. per iterare su stringhe IList (non generiche) – foreach è meglio che for

Alcuni riferimenti trovati sul Web per lo stesso:

  1. Originale grande vecchio articolo di Emmanuel Schanzer
  2. CodeProject FOREACH vs. PER
  3. Blog – foreach o non foreach , questa è la domanda
  4. Forum ASP.NET – NET 1.1 C # for vs foreach

[Modificare]

A parte l’aspetto della leggibilità, sono davvero interessato a fatti e cifre. Esistono applicazioni in cui l’ultimo miglio dell’ottimizzazione delle prestazioni è schiacciato.

Patrick Smacchia ha scritto sul blog questo mese, con le seguenti conclusioni:

  • per i loop su List sono un po ‘più di 2 volte meno costosi di quelli foreach su List.
  • Il ciclo su array è circa 2 volte più economico rispetto al loop su List.
  • Di conseguenza, il looping su array usando for è 5 volte più economico di un loop su List usando foreach (che credo sia quello che facciamo tutti).

foreach cicli foreach dimostrano un intento più specifico rispetto for loop .

L’utilizzo di un ciclo foreach dimostra a chiunque utilizzi il codice che si prevede di fare qualcosa per ciascun membro di una raccolta indipendentemente dalla sua posizione nella raccolta. Mostra anche che non stai modificando la collezione originale (e lancia un’eccezione se provi a).

L’altro vantaggio di foreach è che funziona su qualsiasi object IEnumerable , dove, come per IList , ha senso solo dove ogni elemento ha effettivamente un indice.

Tuttavia, se è necessario utilizzare l’indice di un elemento, ovviamente è necessario utilizzare un ciclo for . Ma se non hai bisogno di usare un indice, averne uno è solo ingombrare il tuo codice.

Non ci sono implicazioni significative sulle prestazioni, per quanto ne so. Ad un certo punto in futuro potrebbe essere più semplice adattare il codice utilizzando foreach per l’esecuzione su più core, ma non è qualcosa di cui preoccuparsi al momento.

Innanzitutto, una contro-rivendicazione della risposta di Dmitry . Per gli array, il compilatore C # emette in gran parte lo stesso codice per foreach come farebbe per un ciclo equivalente. Questo spiega perché per questo benchmark i risultati sono sostanzialmente gli stessi:

 using System; using System.Diagnostics; using System.Linq; class Test { const int Size = 1000000; const int Iterations = 10000; static void Main() { double[] data = new double[Size]; Random rng = new Random(); for (int i=0; i < data.Length; i++) { data[i] = rng.NextDouble(); } double correctSum = data.Sum(); Stopwatch sw = Stopwatch.StartNew(); for (int i=0; i < Iterations; i++) { double sum = 0; for (int j=0; j < data.Length; j++) { sum += data[j]; } if (Math.Abs(sum-correctSum) > 0.1) { Console.WriteLine("Summation failed"); return; } } sw.Stop(); Console.WriteLine("For loop: {0}", sw.ElapsedMilliseconds); sw = Stopwatch.StartNew(); for (int i=0; i < Iterations; i++) { double sum = 0; foreach (double d in data) { sum += d; } if (Math.Abs(sum-correctSum) > 0.1) { Console.WriteLine("Summation failed"); return; } } sw.Stop(); Console.WriteLine("Foreach loop: {0}", sw.ElapsedMilliseconds); } } 

risultati:

 For loop: 16638 Foreach loop: 16529 

Successivamente, convalida il punto di Greg sul fatto che il tipo di raccolta sia importante – cambia la matrice in una List nella precedente, e ottieni risultati radicalmente diversi. Non solo è notevolmente più lento in generale, ma foreach diventa molto più lento dell’accesso per indice. Detto questo, preferirei quasi sempre foreach a un ciclo for in cui rende il codice più semplice – perché la leggibilità è quasi sempre importante, mentre raramente lo è la micro-ottimizzazione.

Ogni volta che ci sono argomenti sulle prestazioni, è sufficiente scrivere un piccolo test in modo da poter utilizzare risultati quantitativi per supportare il tuo caso.

Usa la class StopWatch e ripeti qualcosa qualche milione di volte, per precisione. (Potrebbe essere difficile senza un ciclo for):

 using System.Diagnostics; //... Stopwatch sw = new Stopwatch() sw.Start() for(int i = 0; i < 1000000;i ++) { //do whatever it is you need to time } sw.Stop(); //print out sw.ElapsedMilliseconds 

Le dita incrociate i risultati di questo spettacolo che la differenza è trascurabile, e si potrebbe anche solo fare qualsiasi risultato nel codice più gestibile

Sarà sempre vicino. Per un array, a volte for è leggermente più veloce, ma foreach è più espressivo e offre LINQ, ecc. In generale, attenersi a foreach .

Inoltre, foreach può essere ottimizzato in alcuni scenari. Ad esempio, un elenco collegato potrebbe essere terribile con l’indicizzatore, ma potrebbe essere rapido da parte di foreach . In realtà, lo standard LinkedList non offre nemmeno un indicizzatore per questo motivo.

La mia ipotesi è che probabilmente non sarà significativo nel 99% dei casi, quindi perché dovresti scegliere il più veloce anziché il più appropriato (come nel modo più semplice da capire / mantenere)?

È improbabile che ci sia un’enorme differenza di prestazioni tra i due. Come sempre, di fronte a un “che è più veloce?” domanda, dovresti sempre pensare “Posso misurare questo”.

Scrivi due loop che fanno la stessa cosa nel corpo del loop, esegui e li tempo entrambi, e vedi qual è la differenza di velocità. Fatelo con un corpo quasi vuoto e un corpo ad anello simile a quello che farete effettivamente. Provalo anche con il tipo di raccolta che stai utilizzando, perché diversi tipi di collezioni possono avere caratteristiche di rendimento diverse.

Ci sono ottime ragioni per preferire i cicli di foreach over for loop. Se puoi usare un ciclo foreach , il tuo capo ha ragione che dovresti.

Tuttavia, non ogni iterazione sta semplicemente passando per una lista in ordine uno per uno. Se sta vietando , sì è sbagliato.

Se fossi in te, quello che farei è trasformare tutti i tuoi cicli naturali in ricorsioni . Questo gli avrebbe insegnato, ed è anche un buon esercizio mentale per te.

Jeffrey Richter su TechEd 2005:

“Sono venuto per imparare nel corso degli anni che il compilatore C # è fondamentalmente un bugiardo per me.” .. “Si trova su molte cose.” .. “Come quando fai un ciclo foreach …” .. “… quella è una piccola riga di codice che scrivi, ma ciò che il compilatore C # sputa per farlo è fenomenale. prova / finalmente blocca lì dentro, dentro il blocco finally getta la tua variabile su un’interfaccia IDisposable, e se il cast supera lo chiama il metodo Dispose su di esso, all’interno del ciclo chiama ripetutamente la proprietà Current e il metodo MoveNext all’interno del loop, gli oggetti vengono creati sotto le copertine.Un sacco di persone usa foreach perché è molto facile da codificare, molto facile da fare .. “..” foreach non è molto buono in termini di prestazioni, se si esegue un’iterazione su una raccolta utilizzando invece la piazza notazione delle parentesi, basta fare index, è solo molto più veloce, e non crea nessun object sull’heap … ”

Webcast su richiesta: http://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?EventID=1032292286&EventCategory=3&culture=en-US&CountryCode=US

Questo è ridicolo. Non vi è alcun motivo convincente per vietare il ciclo for, performance-saggio o altro.

Vedi il blog di Jon Skeet per un benchmark delle prestazioni e altri argomenti.

Nei casi in cui si lavora con una collezione di oggetti, foreach è migliore, ma se si incrementa un numero, un ciclo for è migliore.

Nota che nell’ultimo caso potresti fare qualcosa del tipo:

 foreach (int i in Enumerable.Range(1, 10))... 

Ma certamente non ha prestazioni migliori, in realtà ha prestazioni peggiori rispetto a un for .

Questo dovrebbe salvarti:

 public IEnumerator For(int start, int end, int step) { int n = start; while (n < = end) { yield n; n += step; } } 

Uso:

 foreach (int n in For(1, 200, 4)) { Console.WriteLine(n); } 

Per una maggiore vincita, puoi prendere tre delegati come parametri.

Le differenze di velocità in for – e in foreach -loop sono minime quando si passa attraverso strutture comuni come array, elenchi, ecc. E facendo una query LINQ sulla raccolta è quasi sempre leggermente più lento, anche se è più piacevole scrivere! Come hanno detto gli altri poster, cercate espressività piuttosto che un millisecondo di prestazioni extra.

Ciò che non è stato detto finora è che quando un ciclo foreach è compilato, è ottimizzato dal compilatore in base alla collezione su cui sta iterando. Ciò significa che quando non sei sicuro di quale ciclo usare, dovresti usare il ciclo foreach – genererà il ciclo migliore per te quando verrà compilato. È anche più leggibile.

Un altro vantaggio chiave del ciclo foreach è che se l’implementazione della raccolta cambia (da un array int a un List per esempio), il ciclo foreach non richiederà alcuna modifica al codice:

 foreach (int i in myCollection) 

Quanto sopra è lo stesso indipendentemente dal tipo di collezione, mentre nel ciclo for , il seguente non verrà creato se hai cambiato myCollection da una array a una List :

 for (int i = 0; i < myCollection.Length, i++) 

“Ci sono argomenti che potrei usare per aiutarmi a convincerlo che il ciclo for è accettabile da usare?”

No, se il tuo capo è micromanaging al livello di dirti quali costrutti di linguaggio di programmazione utilizzare, non c’è davvero nulla che tu possa dire. Scusate.

Probabilmente dipende dal tipo di raccolta che si sta enumerando e dall’implementazione del suo indicizzatore. In generale, tuttavia, l’uso di foreach potrebbe essere un approccio migliore.

Inoltre, funzionerà con qualsiasi IEnumerable – non solo con gli indicizzatori.

Ogni costrutto linguistico ha un tempo e un luogo appropriati per l’utilizzo. C’è una ragione per cui il linguaggio C # ha quattro istruzioni di iterazione separate: ognuna è presente per uno scopo specifico e ha un uso appropriato.

Consiglio di sederti con il tuo capo e cercare di spiegare razionalmente perché un ciclo for ha uno scopo. Ci sono momentjs in cui un blocco di iterazione descrive in modo più chiaro un algoritmo piuttosto che un’iterazione foreach . Quando questo è vero, è opportuno usarli.

Indicherei anche al tuo capo – Le prestazioni non sono, e non dovrebbero essere un problema in alcun modo pratico – è più una questione di espressione dell’algoritmo in un modo succinto, significativo, manutenibile. Le micro-ottimizzazioni come questa perdono completamente il punto di ottimizzazione delle prestazioni, dal momento che qualsiasi reale vantaggio prestazionale deriverà dalla riprogettazione e dal refactoring algoritmico, non dalla ristrutturazione del ciclo.

Se, dopo una discussione razionale, c’è ancora questa visione autoritaria, sta a te decidere come procedere. Personalmente, non sarei felice di lavorare in un ambiente in cui il pensiero razionale è scoraggiato e prenderei in considerazione il passaggio a un’altra posizione con un altro datore di lavoro. Tuttavia, consiglio vivamente la discussione prima di arrabbiarmi: potrebbe esserci solo un semplice fraintendimento sul posto.

Questo ha le stesse due risposte della maggior parte delle domande “più veloci”:

1) Se non misuri, non lo sai.

2) (Perché …) Dipende.

Dipende da quanto sia costoso il metodo “MoveNext ()”, in relazione al costo del metodo “this [int index]”, per il tipo (o tipi) di IEnumerable su cui si sta iterando.

La parola chiave “foreach” è una scorciatoia per una serie di operazioni – chiama GetEnumerator () una volta su IEnumerable, chiama MoveNext () una volta per iterazione, esegue un controllo di tipo e così via. La cosa che più probabilmente incide sulle misurazioni delle prestazioni è il costo di MoveNext () da quando viene invocato O (N) volte. Forse è economico, ma forse non lo è.

La parola chiave “for” sembra più prevedibile, ma nella maggior parte dei loop “for” troverai qualcosa come “collection [index]”. Sembra una semplice operazione di indicizzazione degli array, ma in realtà è una chiamata al metodo, il cui costo dipende interamente dalla natura della raccolta su cui si sta iterando. Probabilmente è economico, ma forse non lo è.

Se la struttura sottostante della raccolta è essenzialmente un elenco collegato, MoveNext è a basso costo, ma l’indicizzatore potrebbe avere un costo O (N), rendendo il costo reale di un ciclo “per” O (N * N).

È ciò che fai all’interno del ciclo che influisce sulla perfomance, non sul reale costrutto ciclico (supponendo che il tuo caso non sia banale).

Se for è più veloce di foreach è davvero oltre il punto. Dubito seriamente che la scelta dell’uno sull’altro abbia un impatto significativo sulla tua performance.

Il modo migliore per ottimizzare la tua applicazione è attraverso la profilazione del codice reale. Ciò individuerà i metodi che rappresentano il maggior tempo di lavoro / tempo. Ottimizza quelli prima. Se le prestazioni non sono ancora accettabili, ripetere la procedura.

Come regola generale, consiglierei di stare lontano da micro ottimizzazioni poiché raramente produrranno guadagni significativi. L’unica eccezione è quando si ottimizzano i percorsi hot identificati (cioè se la propria profilazione identifica alcuni metodi altamente utilizzati, potrebbe essere sensato ottimizzarli in modo estensivo).

I due funzioneranno quasi esattamente allo stesso modo. Scrivi un codice per usarli entrambi, quindi mostragli l’IL. Dovrebbe mostrare calcoli comparabili, il che significa nessuna differenza nelle prestazioni.

Ho trovato il ciclo foreach che scorre più velocemente attraverso una List . Vedi i miei risultati dei test qui sotto. Nel seguente codice I iterare una array di dimensioni 100, 10000 e 100000 separatamente utilizzando for e ciclo foreach per misurare il tempo.

inserisci la descrizione dell'immagine qui

 private static void MeasureTime() { var array = new int[10000]; var list = array.ToList(); Console.WriteLine("Array size: {0}", array.Length); Console.WriteLine("Array For loop ......"); var stopWatch = Stopwatch.StartNew(); for (int i = 0; i < array.Length; i++) { Thread.Sleep(1); } stopWatch.Stop(); Console.WriteLine("Time take to run the for loop is {0} millisecond", stopWatch.ElapsedMilliseconds); Console.WriteLine(" "); Console.WriteLine("Array Foreach loop ......"); var stopWatch1 = Stopwatch.StartNew(); foreach (var item in array) { Thread.Sleep(1); } stopWatch1.Stop(); Console.WriteLine("Time take to run the foreach loop is {0} millisecond", stopWatch1.ElapsedMilliseconds); Console.WriteLine(" "); Console.WriteLine("List For loop ......"); var stopWatch2 = Stopwatch.StartNew(); for (int i = 0; i < list.Count; i++) { Thread.Sleep(1); } stopWatch2.Stop(); Console.WriteLine("Time take to run the for loop is {0} millisecond", stopWatch2.ElapsedMilliseconds); Console.WriteLine(" "); Console.WriteLine("List Foreach loop ......"); var stopWatch3 = Stopwatch.StartNew(); foreach (var item in list) { Thread.Sleep(1); } stopWatch3.Stop(); Console.WriteLine("Time take to run the foreach loop is {0} millisecond", stopWatch3.ElapsedMilliseconds); } 

AGGIORNATO

Dopo il suggerimento @jgauffin ho usato il codice @johnskeet e ho scoperto che il ciclo for con array è più veloce del seguente,

  • Ciclo Foreach con array.
  • Per loop con lista.
  • Ciclo Foreach con lista.

Vedi i miei risultati dei test e il codice qui sotto,

inserisci la descrizione dell'immagine qui

 private static void MeasureNewTime() { var data = new double[Size]; var rng = new Random(); for (int i = 0; i < data.Length; i++) { data[i] = rng.NextDouble(); } Console.WriteLine("Lenght of array: {0}", data.Length); Console.WriteLine("No. of iteration: {0}", Iterations); Console.WriteLine(" "); double correctSum = data.Sum(); Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i < Iterations; i++) { double sum = 0; for (int j = 0; j < data.Length; j++) { sum += data[j]; } if (Math.Abs(sum - correctSum) > 0.1) { Console.WriteLine("Summation failed"); return; } } sw.Stop(); Console.WriteLine("For loop with Array: {0}", sw.ElapsedMilliseconds); sw = Stopwatch.StartNew(); for (var i = 0; i < Iterations; i++) { double sum = 0; foreach (double d in data) { sum += d; } if (Math.Abs(sum - correctSum) > 0.1) { Console.WriteLine("Summation failed"); return; } } sw.Stop(); Console.WriteLine("Foreach loop with Array: {0}", sw.ElapsedMilliseconds); Console.WriteLine(" "); var dataList = data.ToList(); sw = Stopwatch.StartNew(); for (int i = 0; i < Iterations; i++) { double sum = 0; for (int j = 0; j < dataList.Count; j++) { sum += data[j]; } if (Math.Abs(sum - correctSum) > 0.1) { Console.WriteLine("Summation failed"); return; } } sw.Stop(); Console.WriteLine("For loop with List: {0}", sw.ElapsedMilliseconds); sw = Stopwatch.StartNew(); for (int i = 0; i < Iterations; i++) { double sum = 0; foreach (double d in dataList) { sum += d; } if (Math.Abs(sum - correctSum) > 0.1) { Console.WriteLine("Summation failed"); return; } } sw.Stop(); Console.WriteLine("Foreach loop with List: {0}", sw.ElapsedMilliseconds); } 

perché ha una logica più semplice da implementare, quindi è più veloce di foreach.

A meno che non ci si trovi in ​​uno specifico processo di ottimizzazione della velocità, direi utilizzare qualsiasi metodo produca il codice più semplice da leggere e gestire.

Se un iteratore è già impostato, come in una delle classi di raccolta, allora foreach è una buona opzione facile. E se si tratta di un intervallo intero che stai iterando, allora è probabilmente più pulito.

Jeffrey Richter ha parlato della differenza di rendimento tra for e foreach in un recente podcast: http://pixel8.infragistics.com/shows/everything.aspx#Episode:9317

Nella maggior parte dei casi non c’è davvero differenza.

In genere devi sempre usare foreach quando non hai un indice numerico esplicito, e devi sempre usarlo quando non hai effettivamente una collezione iterabile (ad esempio, iterando su una griglia di array bidimensionale in un triangolo superiore) . Ci sono alcuni casi in cui puoi scegliere.

Si potrebbe obiettare che per i loop può essere un po ‘più difficile da mantenere se i numeri magici iniziano ad apparire nel codice. Dovresti avere ragione ad essere infastidito dal fatto di non essere in grado di utilizzare un ciclo for e di dover creare una raccolta o utilizzare un lambda per creare una sottoraccolta, semplicemente perché i loop sono stati bannati.

Davvero fottiti con la testa e vai per una chiusura IQueryable. Invece:

myList.ForEach (c => Console.WriteLine (c.ToString ());

LOL

Non mi aspetto che nessuno trovi una “enorme” differenza di prestazioni tra i due.

Immagino che la risposta dipenda dal fatto che la raccolta a cui si sta tentando di accedere abbia un’implementazione di accesso indicizzatore più veloce o un’implementazione di accesso IEnumerator più veloce. Poiché IEnumerator utilizza spesso l’indicizzatore e tiene semplicemente una copia della posizione corrente dell’indice, mi aspetto che l’accesso all’enumerator sia almeno lento o lento dell’accesso diretto all’indice, ma non di molto.

Of course this answer doesn’t account for any optimizations the compiler may implement.

Keep in mind that the for-loop and foreach-loop are not always equivalent. List enumerators will throw an exception if the list changes, but you won’t always get that warning with a normal for loop. You might even get a different exception if the list changes at just the wrong time.

It seems a bit strange to totally forbid the use of something like a for loop.

There’s an interesting article here that covers a lot of the performance differences between the two loops.

I would say personally I find foreach a bit more readable over for loops but you should use the best for the job at hand and not have to write extra long code to include a foreach loop if a for loop is more appropriate.

I did test it a while ago, with the result that a for loop is much faster than a foreach loop. The cause is simple, the foreach loop first needs to instantiate an IEnumerator for the collection.