C++ itérateurs & optimisation en boucle
Je vois beaucoup de code c++ qui ressemble à ceci:
for( const_iterator it = list.begin(),
const_iterator ite = list.end();
it != ite; ++it)
Contrairement à la version la plus concise:
for( const_iterator it = list.begin();
it != list.end(); ++it)
Il n'y aura aucune différence de vitesse entre ces deux conventions? Naïvement le premier sera un peu plus rapide depuis la liste.fin() n'est appelée qu'une seule fois. Mais depuis l'itérateur est const, il semble que le compilateur va tirer de ce test de la boucle, de générer l'équivalent de l'assemblée pour les deux.
- La déclaration de " ite " serait une erreur de syntaxe si votre première version est "pour (const_iterator i = liste.begin(), e = liste.end(); i != e; ++i)". Ce n'est qu'un peu plus de caractères que la deuxième forme, donc je viens de l'utiliser par défaut.
- Maintenant en C++11, il est également
for(auto it : list)
qui est essentiellement le second. Mais c'est beaucoup plus agréable. - gamme pour les boucles d'éléments, pas itérateur positions, donc l'équivalent est
for ( const auto& element : list )
- si vous voulez être technique, ce n'est pas le même, parce que les ni sont des itérateurs. Essayez d'accéder à
element->whatever
- hmm? Je pense que c'est le même point, j'ai été ce qui implique que la variable de boucle est un élément, par conséquent doit utiliser
element.whatever
. Droit? - Je l'ai ajouté dans pas parce que c'est exactement baisse de remplacement, mais parce que c'est un moyen d'obtenir la même fonctionnalité/résultat final simplement et facilement.
- la gamme de boucle est équivalent à la première forme, pas le second formulaire (en.cppreference.com/w/cpp/language/range-for)
- mais
const_iterator
signifie seulement "vous ne pouvez pas modifier le conteneur par le biais de cet itérateur", pas " rien d'autre ne peut modifier le conteneur à l'intérieur de cette boucle.
Vous devez vous connecter pour publier un commentaire.
Je ne mentionnerai que pour mémoire que la norme C++ mandats que l'appel
begin()
etend()
sur tout type de conteneur (soitvector
,list
,map
etc.) doit prendre que la constante de temps. Dans la pratique, ces appels seront presque certainement être incorporé à un seul pointeur de la comparaison, si vous compilez avec des optimisations activées.Noter que cette garantie ne vaut pas nécessairement pour plus de fournisseur "conteneurs" qui ne sont pas réellement obéir aux exigences formelles de la un conteneur énoncées dans le chapitre 23 de la norme (par exemple, la liste liée individuellement
slist
).-flto
drapeau pour activer le Lien-l'optimisation du temps'. Clang est un fonctionnalité similaire.Les deux versions ne sont pas le même. Dans la deuxième version, il compare l'itérateur contre
list.end()
à chaque fois, et celist.end()
donne pourrait changer au cours de la boucle. Maintenant, bien sûr, vous ne pouvez pas modifierlist
par le biais de la const_iteratorit
; mais rien n'empêche de code à l'intérieur de la boucle juste des appels de méthodes surlist
directement et de mutation, ce qui pourrait (selon le type de structure de donnéeslist
est) changement de la fin de l'itérateur. Il pourrait donc être incorrecte dans certaines circonstances, pour stocker la fin de l'itérateur d'avance, parce que peut-être plus la bonne fin de l'itérateur par le temps que vous obtenez pour elle.Le premier sera probablement presque toujours être plus rapide, mais si vous pensez que cela fera une différence, toujours profil pour voir qui est plus rapide, et de combien.
Le compilateur sera probablement en mesure de l'inclure l'appel à
end()
dans les deux cas, même siend()
est assez compliqué, il peut choisir de ne pas l'inclure. Toutefois, la clé de l'optimisation est de savoir si ou non le compilateur peut effectuer boucle d'extraction de code invariant. Je voudrais poser que dans la plupart des cas, le compilateur ne peut pas être certain que la valeur deend()
ne changera pas au cours de l'itération de la boucle, dans ce cas, il n'a pas le choix mais pour appelerend()
après chaque itération.Je choisirais l'option qui est la plus concise et lisible. N'essayez pas de deuxième deviner le compilateur et les optimisations qu'il pourrait effectuer. Rappelez-vous que la grande majorité de votre code aura absolument aucun effet sur la performance globale, de sorte que si c'est dans une critique pour les performances de la section de code devrait vous passez l'heure de profil et de choisir une bonne source efficace de la représentation.
Avec une référence spécifique à votre exemple, la première version fait un copie de la
end()
itérateur, en invoquant n'importe quel code s'exécute pour le constructeur de copie de l'itérateur de l'objet. STL en général, les conteneurs contiennent en ligneend()
fonctions, de sorte que le compilateur a beaucoup de chances pour optimiser la deuxième version, même si vous n'êtes pas d'essayer de l'aider à sortir. Laquelle est la meilleure? Les mesurer.Vous pouvez faire la première version plus concise et obtenez le meilleur des deux:
P. S. Les itérateurs ne sont pas const, ils sont des itérateurs pour const référence. Il y a une grosse différence.
Considérons cet exemple:
dans ce cas, vous avez BESOIN de la liste d'appels.fin() dans la boucle, et le compilateur ne va pas optimiser tout ça.
D'autres cas où le compilateur peut prouver que la fin() est retourne toujours la même valeur, l'optimisation peut prendre place.
Si nous parlons des conteneurs STL, que je pense que tout bon compilateur peut optimiser loin multiples end() appels lorsque plusieurs end() appels n'est pas nécessaire pour la programmation logique. Toutefois, si vous avez un conteneur personnalisé et la mise en œuvre de end() n'est pas dans la même unité de traduction, que l'optimisation devront avoir lieu au moment de la liaison. Je sais très peu de choses sur le lien à l'optimisation du temps, mais je parie que la plupart des linkers ne fera pas une telle optimisation.
Aah, les gens semblent être de faire des suppositions. Ouvrez votre code dans le débogueur & vous verrez que les appels à begin(), end (), etc tout est optimisé loin. Il n'est pas nécessaire pour utiliser la version 1. Testé avec le compilateur Visual C++ fullopt.
Le compilateur pourrait être en mesure d'optimiser la deuxième à la première, mais qui suppose que les deux sont équivalents, c'est à dire fin() est en fait constante. Un peu plus problématique, c'est que le compilateur peut être impossible d'en déduire que la fin de l'itérateur est constante en raison d'une possible aliasing. Cependant, en supposant que l'appel à la fin() est incorporé, la différence est juste une mémoire de charge.
Noter que cela suppose que l'optimiseur est activé. Si l'optimiseur n'est pas activé, comme souvent dans les versions de débogage, puis la seconde formulation impliquera N-1 plus d'appels de fonction. Dans les versions actuelles de Visual C++, versions de débogage sera également entraîner des hits en raison de la fonction du prologue/épilogue vérifications et plus lourd de débogage des itérateurs. Par conséquent, dans la STL lourd code, par défaut le premier cas peut empêcher le code de manière disproportionnée lente dans les versions debug.
D'Insertion et de retrait à l'intérieur de la boucle sont une possibilité, comme d'autres l'ont souligné, mais avec ce style de boucle je trouve que peu probable. Pour une chose, nœud à base de conteneurs -- list, set, map -- n'invalident pas la fin() sur chaque opération. Deuxièmement, l'itérateur incrément fréquemment doit être déplacé dans la boucle afin d'éviter l'invalidation des problèmes:
Ainsi, je considère une boucle qui prétend fin d'appel() pour muter-cours-boucle raisons et encore a ++dans la boucle d'en-tête pour être suspect.
De prélever l'échantillon dans des conditions de stress et de voir si vous êtes en * * * * * * ce code très souvent ***.
Si non, il n'a pas d'importance.
Si vous êtes, regardez le démontage, ou en une seule étape.
C'est comment vous pouvez dire qui est plus rapide.
Vous devez être prudent de ces itérateurs.
Ils peuvent obtenir optimisé à nice code machine, mais, assez souvent ils ne le font pas, et de devenir le temps de porcs.
** (Où "en" signifie réellement, ou être appelé à partir d'elle.)
*** (Où "souvent" signifie un pourcentage important de l'époque.)
AJOUTÉ: Ne vous contentez pas de voir combien de fois par seconde le code est exécuté. Il pourrait être de 1 000 fois par seconde et encore être en utilisant moins de 1% du temps.
N'avez pas de temps combien de temps cela prend soit. Il pourrait prendre un millième de seconde et encore moins de 1% du temps.
On pourrait multiplier les deux, pour obtenir une meilleure idée, mais qui ne fonctionne que si ils ne sont pas trop biaisée.
L'échantillonnage de la pile d'appel vous dira si elle utilise un pourcentage élevé suffisamment de temps à la matière.
J'ai toujours préféré le premier. Mais avec les fonctions inline, les optimisations du compilateur et relativement plus petite de la taille du conteneur ( dans mon cas, c'est normalement max de 20 à 25 points) il ne veut vraiment pas faire de grande différence par rapport à la performance.
Mais récemment, je suis en utilisant plus de std::for_each partout où c'est possible. Son optimisé en boucle qui permet de rendre le code plus lisible que les deux autres.
Je vais utiliser la boucle uniquement lorsque
std::for_each
ne peut pas être utilisé. (par ex:std::for_each
ne vous permet pas de briser la boucle, sauf si une exception est levée).En théorie, le compilateur peut optimiser la deuxième version dans le premier (en supposant que le conteneur ne change pas au cours de la boucle, évidemment).
Dans la pratique, j'ai trouvé plusieurs cas similaires lors de l'analyse des critiques de code où mon compilateur a totalement échoué à hisser invariant calculs de boucle conditions. Ainsi, alors que le un peu plus concis version est bien dans la plupart des cas, je ne vous fiez pas au compilateur de faire les choses sensibles avec elle pour un cas où je suis très inquiète à propos de la performance.