Dans .NET, quelle boucle s’exécute plus rapidement, “pour” ou “foreach”?

Dans C # / VB.NET / .NET, quelle boucle s’exécute plus vite, for ou for ?

Depuis que j’ai lu qu’une boucle for fonctionne plus vite qu’une boucle foreach a longtemps, j’ai supposé qu’elle était vraie pour toutes les collections, collections génériques, tous les tableaux, etc.

J’ai parcouru Google et trouvé quelques articles, mais la plupart ne sont pas concluants (lire les commentaires sur les articles) et ouverts.

L’idéal serait que chaque scénario soit répertorié et que la solution soit la meilleure.

Par exemple (juste un exemple de ce que cela devrait être):

  1. pour itérer un tableau de plus de 1000 chaînes – for vaut mieux que foreach
  2. pour itérer sur des chaînes IList (non génériques) – foreach vaut mieux que for

Quelques références trouvées sur le web pour le même:

  1. Ancien grand article original d’Emmanuel Schanzer
  2. CodeProject VERS AVANT POUR
  3. Blog – foreach ou non foreach , c’est la question
  4. Forum ASP.NET – NET 1.1 C # for vs foreach

[Modifier]

Outre l’aspect de la lisibilité, je m’intéresse vraiment aux faits et aux chiffres. Il y a des applications où le dernier kilomètre d’optimisation des performances réduit au minimum.

Pasortingck Smacchia a commenté ce mois-ci avec les conclusions suivantes:

  • pour les boucles sur la liste sont un peu plus de 2 fois moins cher que les boucles foreach sur la liste.
  • Le bouclage sur le réseau est environ 2 fois moins cher que le bouclage sur List.
  • En conséquence, le bouclage sur l’utilisation du tableau est 5 fois moins cher que la mise en boucle sur List en utilisant foreach (ce que je pense, c’est ce que nous faisons tous).

foreach boucles foreach démontrent une intention plus spécifique que for boucles .

L’utilisation d’une boucle foreach montre à toute personne utilisant votre code que vous envisagez de faire quelque chose pour chaque membre d’une collection, indépendamment de sa place dans la collection. Il montre également que vous ne modifiez pas la collection d’origine (et lance une exception si vous essayez de).

L’autre avantage de foreach est qu’il fonctionne sur n’importe quel IEnumerable , où quant à IList , chaque élément a un index.

Cependant, si vous devez utiliser l’index d’un élément, vous devriez bien sûr être autorisé à utiliser une boucle for . Mais si vous n’avez pas besoin d’utiliser un index, il suffit d’encombrer votre code.

Pour autant que je sache, il n’ya pas d’impact significatif sur les performances. À un moment donné, il sera peut-être plus facile d’adapter le code en utilisant foreach pour qu’il s’exécute sur plusieurs cœurs, mais ce n’est pas une chose à craindre pour le moment.

Tout d’abord, une demande reconventionnelle à la réponse de Dmitry . Pour les tableaux, le compilateur C # émet en grande partie le même code pour foreach que pour un équivalent for boucle. Cela explique pourquoi pour ce benchmark, les résultats sont fondamentalement les mêmes:

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

Résultats:

 For loop: 16638 Foreach loop: 16529 

Ensuite, la validation que le sharepoint Greg concernant le type de collection est important – changez le tableau en une List dans ce qui précède, et vous obtiendrez des résultats radicalement différents. En général, non seulement cela ralentit de manière significative, mais le temps d’access devient beaucoup plus lent que celui d’access par index. Cela dit, je préférerais presque toujours que foreach devienne une boucle for, ce qui simplifie le code, car la lisibilité est presque toujours importante, alors que la micro-optimisation l’est rarement.

Chaque fois qu’il y a des arguments sur la performance, il vous suffit d’écrire un petit test afin de pouvoir utiliser vos résultats quantitatifs.

Utilisez la classe StopWatch et répétez quelque chose quelques millions de fois, pour plus de précision. (Cela pourrait être difficile sans une boucle 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 

Les doigts croisés les résultats de cette montre que la différence est négligeable, et vous pourriez tout aussi bien faire tout ce qui résulte dans le code le plus maintenable

Ce sera toujours proche. Pour un tableau, parfois for est légèrement plus rapide, mais foreach est plus expressif, et offre LINQ, etc. En général, restr avec foreach .

En outre, foreach peut être optimisé dans certains scénarios. Par exemple, une liste chaînée peut être terrible par indexeur, mais elle peut être rapide par foreach . En fait, le LinkedList standard ne propose même pas d’indexeur pour cette raison.

Je suppose que cela ne sera probablement pas significatif dans 99% des cas, alors pourquoi choisiriez-vous le plus rapide au lieu du plus approprié (comme dans le plus facile à comprendre / à maintenir)?

Il est peu probable qu’il y ait une énorme différence de performance entre les deux. Comme toujours, face à un “qui est plus rapide?” question, vous devriez toujours penser “je peux mesurer cela”.

Écrivez deux boucles qui font la même chose dans le corps de la boucle, exécutez-les et chronométrez-les toutes les deux, et voyez quelle est la différence de vitesse. Faites-le avec un corps presque vide et un corps de boucle similaire à ce que vous allez réellement faire. Essayez également avec le type de collection que vous utilisez, car différents types de collections peuvent avoir des caractéristiques de performances différentes.

Il y a de très bonnes raisons de préférer les boucles foreach for boucles. Si vous pouvez utiliser une boucle foreach , votre patron a raison de le faire.

Cependant, toutes les itérations ne passent pas toutes par une liste dans l’ordre une par une. S’il l’ interdit , oui, c’est faux.

Si j’étais vous, ce que je ferais serait de transformer toutes vos boucles naturelles en récursions . Cela lui apprendrait, et c’est aussi un bon exercice mental pour vous.

Jeffrey Richter sur TechEd 2005:

“Je suis venu au fil des ans pour apprendre que le compilateur C # est fondamentalement un menteur pour moi.” .. “Il repose sur beaucoup de choses.” .. “Comme quand vous faites une boucle foreach …” .. “… c’est une petite ligne de code que vous écrivez, mais ce que le compilateur C # fait pour le faire est phénoménal. try / finally se bloque là, dans le bloc finally, il jette votre variable dans une interface IDisposable, et si la dissortingbution réussit, elle appelle la méthode Dispose, dans la boucle, elle appelle la propriété Current et la méthode MoveNext à plusieurs resockets dans la boucle, des objects sont créés sous les couvertures. Beaucoup de gens utilisent foreach parce que c’est très facile à coder, très facile à faire .. “..” foreach n’est pas très bon en termes de performances, si vous avez parcouru une collection à la place notation entre crochets, juste faire de l’index, c’est juste beaucoup plus rapide, et cela ne crée pas d’objects sur le tas … ”

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

C’est ridicule. Il n’y a aucune raison impérieuse d’interdire le for-loop, la performance ou autre.

Voir le blog de Jon Skeet pour un benchmark de performance et d’autres arguments.

Dans les cas où vous travaillez avec une collection d’objects, foreach est mieux, mais si vous incrémentez un nombre, une boucle for est préférable.

Notez que dans le dernier cas, vous pourriez faire quelque chose comme:

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

Mais cela ne fonctionne certainement pas mieux, il a en fait une moins bonne performance que pour un for .

Cela devrait vous sauver:

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

Utilisation:

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

Pour gagner plus, vous pouvez prendre trois delegates comme parameters.

Les différences de vitesse dans une boucle for – et foreach sont minuscules lorsque vous parcourez des structures courantes comme des tableaux, des listes, etc., et que faire une requête LINQ sur la collection est presque toujours un peu plus lent, bien qu’il soit préférable d’écrire! Comme les autres affiches l’ont dit, privilégiez l’expressivité plutôt que la milliseconde de performance supplémentaire.

Ce qui n’a pas encore été dit, c’est que lorsqu’une boucle foreach est compilée, elle est optimisée par le compilateur en fonction de la collection sur laquelle elle est itérée. Cela signifie que lorsque vous n’êtes pas sûr de la boucle à utiliser, vous devriez utiliser la boucle foreach – elle génèrera la meilleure boucle pour vous quand elle sera compilée. C’est plus lisible aussi.

Un autre avantage clé de la boucle foreach est que si votre implémentation de collection change (d’un array int à une List par exemple), votre boucle foreach ne nécessitera aucune modification de code:

 foreach (int i in myCollection) 

Ce qui précède est identique quel que soit le type de votre collection, alors que dans votre boucle for , les éléments suivants ne seront pas myCollection si vous avez modifié myCollection d’un array à une List :

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

“Y a-t-il des arguments que je pourrais utiliser pour m’aider à le convaincre que la boucle est acceptable?”

Non, si votre patron gère au mieux le langage de programmation à utiliser, vous ne pouvez rien dire. Pardon.

Cela dépend probablement du type de collection que vous énumérez et de l’implémentation de son indexeur. En général, l’utilisation de foreach sera probablement une meilleure approche.

En outre, cela fonctionnera avec n’importe quel IEnumerable – et pas seulement avec les indexeurs.

Chaque construction de langue a un moment et un lieu appropriés pour son utilisation. Il y a une raison pour laquelle le langage C # dispose de quatre instructions d’itération distinctes – chacune existe dans un but spécifique et a une utilisation appropriée.

Je recommande de s’asseoir avec votre patron et d’essayer d’expliquer rationnellement pourquoi une boucle for a un but. Il y a des moments où un bloc for iteration décrit plus clairement un algorithme qu’une itération foreach . Lorsque cela est vrai, il convient de les utiliser.

Je ferais également remarquer à votre patron – La performance n’est pas et ne devrait pas poser de problème pratique – c’est plus une question d’expression de l’algorithme d’une manière succincte, significative et maintenable. De telles optimisations manquent complètement d’optimisation des performances, car tout avantage réel en termes de performances proviendra de la refonte et de la refonte algorithmiques, et non de la restructuration des boucles.

Si, après une discussion rationnelle, il y a toujours cette vision autoritaire, c’est à vous de décider comment procéder. Personnellement, je ne serais pas heureux de travailler dans un environnement où la pensée rationnelle est découragée et envisagerait de passer à un autre poste chez un autre employeur. Cependant, je recommande fortement la discussion avant de me fâcher – il y a peut-être un simple malentendu.

Cela a les mêmes deux réponses que la plupart des questions “qui est plus rapide”:

1) Si vous ne mesurez pas, vous ne savez pas.

2) (parce que …) Cela dépend.

Cela dépend du prix de la méthode “MoveNext ()” par rapport au coût de la méthode “this [int index]” pour le ou les types de IEnumerable que vous allez parcourir.

Le mot clé “foreach” est un raccourci pour une série d’opérations – il appelle GetEnumerator () une fois sur le IEnumerable, il appelle MoveNext () une fois par itération, effectue une vérification de type, etc. Le coût de MoveNext () est le facteur le plus susceptible d’avoir un impact sur les mesures de performance car il reçoit les appels O (N). Peut-être que c’est pas cher, mais peut-être pas.

Le mot-clé “for” semble plus prévisible, mais dans la plupart des boucles “for”, vous trouverez quelque chose comme “collection [index]”. Cela ressemble à une opération d’indexation de tableau simple, mais c’est en fait un appel de méthode, dont le coût dépend entièrement de la nature de la collection que vous parcourez. C’est probablement bon marché, mais peut-être pas.

Si la structure sous-jacente de la collection est essentiellement une liste liée, MoveNext est peu coûteux, mais l’indexeur peut avoir un coût O (N), ce qui rend le coût réel d’une boucle “for” O (N * N).

C’est ce que vous faites à l’ intérieur de la boucle qui affecte la performance, pas la structure de boucle (en supposant que votre cas n’est pas sortingvial).

Que ce soit for est plus rapide que foreach est vraiment en dehors du point. Je doute sérieusement que choisir l’un sur l’autre aura un impact significatif sur votre performance.

La meilleure façon d’optimiser votre application consiste à profiler le code actuel. Cela permettra d’identifier les méthodes qui comptent le plus de travail / temps. Optimisez les premiers. Si les performances ne sont toujours pas acceptables, répétez la procédure.

En règle générale, je recommanderais de ne pas utiliser les micro-optimisations, car celles-ci généreront rarement des gains significatifs. La seule exception concerne l’optimisation des chemins d’access identifiés (par exemple, si votre profilage identifie quelques méthodes très utilisées, il peut être judicieux de les optimiser de manière approfondie).

Les deux fonctionneront presque de la même manière. Ecrivez du code pour utiliser les deux, puis montrez-lui l’IL. Il devrait montrer des calculs comparables, ce qui signifie pas de différence de performance.

J’ai trouvé la boucle foreach qui parcourait une List plus rapidement . Voir mes résultats de test ci-dessous. Dans le code ci-dessous, je réitère un array de taille 100, 10000 et 100000 séparément en utilisant la boucle for et foreach pour mesurer le temps.

entrer la description de l'image ici

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

ACTUALISÉ

Après la suggestion de @jgauffin, j'ai utilisé le code @johnskeet et constaté que la boucle for avec array était plus rapide que la suivante,

  • Boucle Foreach avec tableau.
  • Pour boucle avec liste.
  • Boucle Foreach avec liste.

Voir mes résultats de test et le code ci-dessous,

entrer la description de l'image ici

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

car a une logique plus simple à implémenter, donc plus rapide que foreach.

À moins que vous n’ayez un processus spécifique d’optimisation de la vitesse, je dirais que la méthode la plus facile à lire et à conserver du code est la suivante.

Si un iterator est déjà configuré, comme pour l’une des classes de collection, le foreach est une bonne option. Et si c’est une plage entière que vous itérez, alors pour est probablement plus propre.

Jeffrey Richter a parlé de la différence de performance entre for et foreach sur un podcast récent: http://pixel8.infragistics.com/shows/everything.aspx#Episode:9317

Dans la plupart des cas, il n’y a vraiment aucune différence.

Généralement, vous devez toujours utiliser foreach lorsque vous n’avez pas d’index numérique explicite, et vous devez toujours utiliser lorsque vous ne disposez pas d’une collection itérable (par exemple, itération sur une grid de tableau à deux dimensions dans un sortingangle supérieur). . Il y a des cas où vous avez le choix.

On pourrait soutenir que les boucles peuvent être un peu plus difficiles à gérer si des nombres magiques apparaissent dans le code. Vous devriez avoir raison de ne pas pouvoir utiliser une boucle for et de créer une collection ou utiliser un lambda pour créer une sous-collection simplement parce que les boucles ont été bannies.

Vraiment visser avec sa tête et opter pour une fermeture IQueryable.

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

LOL

Je ne m’attendrais pas à ce que quelqu’un trouve une “énorme” différence de performance entre les deux.

Je suppose que la réponse dépend du fait que la collection à laquelle vous tentez d’accéder dispose d’une implémentation plus rapide de l’indexeur ou d’une implémentation plus rapide de l’access IEnumerator. Comme IEnumerator utilise souvent l’indexeur et ne contient qu’une copie de la position d’index en cours, je m’attendrais à ce que l’access à l’énumérateur soit au moins aussi lent ou plus lent que l’access direct à l’index, mais pas beaucoup.

Bien sûr, cette réponse ne tient pas compte des optimisations que le compilateur peut implémenter.

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.