Quel extrait de code offrira de meilleures performances? Les segments de code ci-dessous ont été écrits en C #.
1.
for(int counter=0; counter<list.Count; counter++)
{
list[counter].DoSomething();
}
2.
foreach(MyType current in list)
{
current.DoSomething();
}
c#
performance
for-loop
foreach
Kthevar
la source
la source
list
vraiment uncount
membre à la place deCount
.Réponses:
Eh bien, cela dépend en partie du type exact de
list
. Cela dépendra également du CLR exact que vous utilisez.Que ce soit significatif ou non dépendra du fait que vous effectuez un vrai travail en boucle. Dans presque tous les cas, la différence de performance ne sera pas significative, mais la différence de lisibilité favorise la
foreach
boucle.J'utiliserais personnellement LINQ pour éviter aussi le "si":
EDIT: Pour ceux d'entre vous qui prétendent que l'itération sur un
List<T>
avecforeach
produit le même code que lafor
boucle, voici la preuve que ce n'est pas le cas:Produit IL de:
Le compilateur traite les tableaux différemment, convertissant une
foreach
boucle en unefor
boucle, mais pasList<T>
. Voici le code équivalent pour un tableau:Fait intéressant, je ne peux trouver cela documenté dans la spécification C # 3 nulle part ...
la source
List<T>
.foreach
sur un tableau équivaut defor
toute façon. Commencez toujours par coder pour la lisibilité, puis ne micro-optimisez que lorsque vous avez la preuve que cela donne un avantage mesurable en termes de performances.Une
for
boucle est compilée en code à peu près équivalent à ceci:Où une
foreach
boucle est compilée en code à peu près équivalent à ceci:Donc, comme vous pouvez le voir, tout dépendrait de la façon dont l'énumérateur est implémenté par rapport à la façon dont l'indexeur de listes est implémenté. Il s'avère que l'énumérateur pour les types basés sur des tableaux est normalement écrit quelque chose comme ceci:
Donc, comme vous pouvez le voir, dans ce cas, cela ne fera pas beaucoup de différence, mais l'énumérateur d'une liste liée ressemblerait probablement à ceci:
Dans .NET, vous constaterez que la classe LinkedList <T> n'a même pas d'indexeur, vous ne pourrez donc pas faire votre boucle for sur une liste liée; mais si vous pouviez, l'indexeur devrait être écrit comme ceci:
Comme vous pouvez le voir, appeler cela plusieurs fois dans une boucle sera beaucoup plus lent que d'utiliser un énumérateur qui peut se rappeler où il se trouve dans la liste.
la source
Un test facile à semi-valider. J'ai fait un petit test, juste pour voir. Voici le code:
Et voici la section foreach:
Lorsque j'ai remplacé le for par un foreach - le foreach était 20 millisecondes plus rapide - de manière cohérente . Le pour était de 135-139ms tandis que le foreach était de 113-119ms. J'ai échangé plusieurs fois d'avant en arrière, m'assurant que ce n'était pas un processus qui venait juste de démarrer.
Cependant, lorsque j'ai supprimé l'instruction foo et if, le for était plus rapide de 30 ms (foreach était de 88 ms et for était de 59 ms). C'étaient tous les deux des coquilles vides. Je suppose que le foreach a en fait passé une variable alors que le for incrémentait simplement une variable. Si j'ai ajouté
Ensuite, le for devient lent d'environ 30 ms. Je suppose que cela a à voir avec la création de foo et la saisie de la variable dans le tableau et son affectation à foo. Si vous accédez simplement à intList [i], vous n'avez pas cette pénalité.
En toute honnêteté, je m'attendais à ce que le foreach soit légèrement plus lent dans toutes les circonstances, mais pas assez pour avoir de l'importance dans la plupart des applications.
edit: voici le nouveau code utilisant les suggestions Jons (134217728 est le plus grand int que vous puissiez avoir avant que l'exception System.OutOfMemory ne soit levée):
Et voici les résultats:
Générer des données. Calcul de la boucle for: 2458ms Calcul de la boucle foreach: 2005ms
Les échanger pour voir si cela traite de l'ordre des choses donne (presque) les mêmes résultats.
la source
Remarque: cette réponse s'applique plus à Java qu'à C #, car C # n'a pas d'indexeur
LinkedLists
, mais je pense que le point général est toujours valable.Si
list
vous travaillez avec unLinkedList
, les performances du code indexeur ( accès de type tableau ) sont bien pires que l'utilisation deIEnumerator
from theforeach
, pour de grandes listes.Lorsque vous accédez à l'élément 10.000 dans a en
LinkedList
utilisant la syntaxe de l'indexeur:,list[10000]
la liste chaînée commencera au nœud principal et traversera leNext
pointeur dix mille fois, jusqu'à ce qu'elle atteigne l'objet correct. Évidemment, si vous faites cela en boucle, vous obtiendrez:Lorsque vous appelez
GetEnumerator
(en utilisant implicitement laforach
syntaxe -syntaxe), vous obtiendrez unIEnumerator
objet qui a un pointeur vers le nœud principal. Chaque fois que vous appelezMoveNext
, ce pointeur est déplacé vers le nœud suivant, comme ceci:Comme vous pouvez le voir, dans le cas de
LinkedList
s, la méthode d'indexation de tableau devient de plus en plus lente, plus la boucle est longue (elle doit passer par le même pointeur de tête encore et encore). Alors que leIEnumerable
juste fonctionne en temps constant.Bien sûr, comme Jon l'a dit, cela dépend vraiment du type de
list
, si lelist
n'est pas unLinkedList
, mais un tableau, le comportement est complètement différent.la source
LinkedList<T>
documentation sur MSDN, et il a une API assez décente. Plus important encore, il n'a pas deget(int index)
méthode, comme Java. Pourtant, je suppose que le point est toujours valable pour toute autre structure de données de type liste qui expose un indexeur plus lent qu'un spécifiqueIEnumerator
.Comme d'autres personnes l'ont mentionné, bien que les performances n'aient pas vraiment d'importance, le foreach sera toujours un peu plus lent à cause de l' utilisation
IEnumerable
/IEnumerator
dans la boucle. Le compilateur traduit la construction en appels sur cette interface et pour chaque étape une fonction + une propriété est appelée dans la construction foreach.C'est le développement équivalent de la construction en C #. Vous pouvez imaginer comment l'impact sur les performances peut varier en fonction des implémentations de MoveNext et Current. Alors que dans un accès à un tableau, vous n'avez pas ces dépendances.
la source
List<T>
ici, alors il y a toujours le hit (éventuellement en ligne) d'appeler l'indexeur. Ce n'est pas comme s'il s'agissait d'un accès à un tableau nu.Après avoir lu suffisamment d'arguments pour dire que "la boucle foreach devrait être préférée pour la lisibilité", je peux dire que ma première réaction a été "quoi"? La lisibilité, en général, est subjective et, dans ce cas particulier, encore plus. Pour quelqu'un avec une formation en programmation (pratiquement, tous les langages avant Java), les boucles for sont beaucoup plus faciles à lire que les boucles foreach. De plus, les mêmes personnes affirmant que les boucles foreach sont plus lisibles, sont également des partisans de linq et d'autres «fonctionnalités» qui rendent le code difficile à lire et à maintenir, ce qui prouve le point ci-dessus.
À propos de l'impact sur les performances, voyez la réponse à cette question.
EDIT: Il existe des collections en C # (comme le HashSet) qui n'ont pas d'indexeur. Dans ces collections, foreach est le seul moyen d'itérer et il est le seul cas , je pense qu'il devrait être utilisé plus pour .
la source
Il y a un autre fait intéressant qui peut être facilement manqué lors du test de la vitesse des deux boucles: l'utilisation du mode de débogage ne permet pas au compilateur d'optimiser le code en utilisant les paramètres par défaut.
Cela m'a conduit au résultat intéressant que foreach est plus rapide qu'en mode débogage. Alors que le for est plus rapide que foreach en mode release. De toute évidence, le compilateur a de meilleures façons d'optimiser une boucle for qu'une boucle foreach qui compromet plusieurs appels de méthode. Une boucle for est d'ailleurs si fondamentale qu'il est possible qu'elle soit même optimisée par le processeur lui-même.
la source
Dans l'exemple que vous avez fourni, il est certainement préférable d'utiliser une
foreach
boucle plutôt qu'unefor
boucle.La
foreach
construction standard peut être plus rapide (1,5 cycles par étape) qu'une simplefor-loop
(2 cycles par étape), à moins que la boucle n'ait été déroulée (1,0 cycle par étape).Donc , pour le code de tous les jours, les performances ne sont pas une raison d'utiliser les plus complexes
for
,while
ou desdo-while
constructions.Consultez ce lien: http://www.codeproject.com/Articles/146797/Fast-and-Less-Fast-Loops-in-C
la source
vous pouvez en savoir plus sur Deep .NET - partie 1 Itération
il couvre les résultats (sans la première initialisation) du code source .NET jusqu'au désassemblage.
par exemple - Array Itération avec une boucle foreach:
et - itération de liste avec la boucle foreach:
et les résultats finaux:
la source