J'ai eu cette idée de cette question sur stackoverflow.com
Le modèle suivant est courant:
final x = 10;//whatever constant value
for(int i = 0; i < Math.floor(Math.sqrt(x)) + 1; i++) {
//...do something
}
Le point que j'essaie de faire est que l'énoncé conditionnel est quelque chose de compliqué et ne change pas.
Est-il préférable de le déclarer dans la section d'initialisation de la boucle, en tant que tel?
final x = 10;//whatever constant value
for(int i = 0, j = Math.floor(Math.sqrt(x)) + 1; i < j; i++) {
//...do something
}
Est-ce plus clair?
Que faire si l'expression conditionnelle est simple, comme
final x = 10;//whatever constant value
for(int i = 0, j = n*n; i > j; j++) {
//...do something
}
java
c++
coding-style
language-agnostic
Celeritas
la source
la source
x
est de grande ampleur,Math.floor(Math.sqrt(x))+1
est égal àMath.floor(Math.sqrt(x))
. :-){ x=whatever; for (...) {...} }
ou, mieux encore, demandez-vous s'il y a suffisamment de choses pour que ce soit une fonction distincte.Réponses:
Ce que je ferais, c'est quelque chose comme ça:
Honnêtement, la seule bonne raison de cram initialiser
j
(maintenantlimit
) dans l'en-tête de boucle est de le garder correctement défini. Tout ce qu'il faut pour faire en sorte qu'un non-problème soit une belle portée de fermeture étanche.Je peux apprécier le désir d'être rapide mais ne sacrifie pas la lisibilité sans une vraie bonne raison.
Bien sûr, le compilateur peut être optimisé, l'initialisation de plusieurs variables peut être légale, mais les boucles sont suffisamment difficiles à déboguer telles quelles. Veuillez être gentil avec les humains. Si cela nous ralentit vraiment, c'est bien de le comprendre suffisamment pour le réparer.
la source
for(int i = 0; i < n*n; i++){...}
vous n'assigneriez pasn*n
à une variable, n'est-ce pas?final
). Qui se soucie si une constante qui a une application du compilateur l'empêchant de changer est accessible plus tard dans la fonction?Un bon compilateur générera le même code de toute façon, donc si vous recherchez des performances, n'effectuez un changement que s'il se trouve dans une boucle critique et que vous l'avez réellement profilé et trouvé que cela fait une différence. Même si le compilateur ne peut pas l'optimiser, comme les gens l'ont souligné dans les commentaires sur le cas des appels de fonction, dans la grande majorité des situations, la différence de performances va être trop petite pour mériter la considération d'un programmeur.
Toutefois...
Nous ne devons pas oublier que le code est principalement un moyen de communication entre les humains, et vos deux options ne communiquent pas très bien aux autres humains. La première donne l'impression que l'expression doit être calculée à chaque itération, et la seconde étant dans la section d'initialisation implique qu'elle sera mise à jour quelque part dans la boucle, où elle est vraiment constante tout au long.
Je préférerais en fait qu'il soit retiré au-dessus de la boucle et fait
final
pour que cela soit immédiatement et abondamment clair pour quiconque lit le code. Ce n'est pas idéal non plus car cela augmente la portée de la variable, mais votre fonction englobante ne devrait pas contenir beaucoup plus que cette boucle de toute façon.la source
Comme l'a dit @Karl Bielefeldt dans sa réponse, ce n'est généralement pas un problème.
Cependant, il s'agissait à un moment d'un problème courant en C et C ++, et une astuce est survenue pour contourner le problème sans réduire la lisibilité du code - itérer en arrière, jusqu'à
0
.Maintenant, le conditionnel dans chaque itération est exactement
>= 0
ce que chaque compilateur compilera en 1 ou 2 instructions d'assemblage. Chaque processeur fabriqué au cours des dernières décennies devrait avoir des vérifications de base comme celles-ci; en faisant une vérification rapide sur ma machine x64, je vois que cela se transforme de manière prévisible encmpl $0x0, -0x14(%rbp)
(valeur de comparaison longue-int 0 vs registre rbp décalé -14) etjl 0x100000f59
( passez à l'instruction suivant la boucle si la comparaison précédente était vraie pour "2nd-arg <1er argument ») .Notez que j'ai supprimé le
+ 1
deMath.floor(Math.sqrt(x)) + 1
; pour que les mathématiques fonctionnent, la valeur de départ doit êtreint i = «iterationCount» - 1
. Il convient également de noter que votre itérateur doit être signé;unsigned int
ne fonctionnera pas et avertira probablement le compilateur.Après avoir programmé dans des langages basés sur C pendant environ 20 ans, je n'écris maintenant que des boucles d'itération d'index inversé, sauf s'il existe une raison spécifique de réitérer l'index. En plus des vérifications plus simples dans les conditions, l'itération inversée passe souvent également à côté de ce qui serait autrement des mutations de tableau gênantes pendant l'itération.
la source
unsigned
compteurs fonctionneront ici si vous modifiez la vérification (le moyen le plus simple consiste à ajouter la même valeur des deux côtés); par exemple, pour tout décrémentDec
, la vérification(i + Dec) >= Dec
doit toujours avoir le même résultat que lasigned
vérificationi >= 0
, avec les deuxsigned
et lesunsigned
compteurs, tant que le langage a des règles enveloppantes bien définies pour lesunsigned
variables (en particulier,-n + n == 0
doit être vrai pour les deuxsigned
etunsigned
). Notez cependant que cela peut être moins efficace qu'une>=0
vérification signée , si le compilateur n'a pas d'optimisation pour cela.Dec
constante à la fois à la valeur de départ et à la valeur de fin fonctionne, mais la rend beaucoup moins intuitive, et si vous l'utilisezi
comme index de tableau, vous devrez également faire ununsigned int arrayI = i - Dec;
dans le corps de la boucle. J'utilise simplement l'itération directe lorsque je suis coincé avec un itérateur non signé; souvent avec unei <= count - 1
conditionnelle pour garder la logique parallèle aux boucles d'itération inverse.Dec
spécifiquement ajouter aux valeurs de début et de fin, mais déplacer la vérification de l'état desDec
deux côtés.for (unsigned i = N - 1; i + 1 >= 1; i--) /*...*/
Cela vous permet de l'utiliseri
normalement dans la boucle, tout en garantissant que la valeur la plus basse possible sur le côté gauche de la condition est0
(pour empêcher le bouclage d'interférer). Il est certainement beaucoup plus simple d'utiliser l'itération directe lorsque vous travaillez avec des compteurs non signés.Cela devient intéressant une fois que Math.sqrt (x) est remplacé par Mymodule.SomeNonPureMethodWithSideEffects (x).
Fondamentalement, mon modus operandi est le suivant: si l'on s'attend à ce que quelque chose donne toujours la même valeur, alors ne l'évaluez qu'une seule fois. Par exemple, List.Count, si la liste est censée ne pas changer pendant le fonctionnement de la boucle, puis obtenez le nombre en dehors de la boucle dans une autre variable.
Certains de ces "décomptes" peuvent être étonnamment coûteux, en particulier lorsque vous traitez avec des bases de données. Même si vous travaillez sur un ensemble de données qui n'est pas censé changer pendant l'itération de la liste.
la source
for( auto it = begin(dataset); !at_end(it); ++it )
À mon avis, c'est très spécifique à la langue. Par exemple, si j'utilise C ++ 11, je soupçonne que si la vérification de l'état est un
constexpr
fonction, le compilateur serait très susceptible d'optimiser les multiples exécutions car il sait qu'il produira la même valeur à chaque fois.Cependant, si l'appel de fonction est une fonction de bibliothèque qui n'est pas
constexpr
le compilateur, il va presque certainement l'exécuter à chaque itération car il ne peut pas en déduire (sauf s'il est en ligne et peut donc être déduit comme pur).Je connais moins Java, mais étant donné que JIT est compilé, je suppose que le compilateur a suffisamment d'informations à l'exécution pour probablement intégrer et optimiser la condition. Mais cela reposerait sur une bonne conception du compilateur et le compilateur décidant cette boucle était une priorité d'optimisation que nous ne pouvons que deviner.
Personnellement , je pense qu'il est un peu plus élégante de mettre la condition dans la boucle si vous le pouvez, mais si elle est complexe , je l'écrire dans une
constexpr
ouinline
fonction, ou vos langues equivalant à laisser entendre que la fonction est pure et optimisable. Cela rend l'intention évidente et maintient le style de boucle idiomatique sans créer une énorme ligne illisible. Il donne également à la vérification de condition un nom s'il s'agit de sa propre fonction afin que les lecteurs puissent immédiatement voir de façon logique à quoi sert la vérification sans la lire si elle est complexe.la source