Déclarer un object à l’intérieur ou à l’extérieur d’une boucle?

Existe-t-il une pénalité de performance pour l’extrait de code suivant?

for (int i=0; i<someValue; i++) { Object o = someList.get(i); o.doSomething; } 

Ou est-ce que ce code a plus de sens?

 Object o; for (int i=0; i<someValue; i++) { o = someList.get(i); o.doSomething; } 

Si en code octet ces deux sont totalement équivalents alors évidemment la première méthode semble meilleure en termes de style, mais je veux m’assurer que c’est le cas.

Dans les compilateurs d’aujourd’hui, non. Je déclare les objects dans la plus petite scope possible, car c’est beaucoup plus lisible pour le prochain.

Pour citer Knuth, qui pourrait citer Hoare :

L’optimisation prématurée est la racine de tout Mal.

Le fait que le compilateur produise un code légèrement plus rapide en définissant la variable en dehors de la boucle est discutable, et j’imagine que ce ne sera pas le cas. Je suppose que cela produira un bytecode identique.

Comparez cela avec le nombre d’erreurs que vous éviterez probablement en contrôlant correctement votre variable en utilisant la déclaration en boucle …

Il n’y a pas de pénalité de performance pour déclarer l’object o dans la boucle. Le compilateur génère un bytecode très similaire et effectue les optimisations correctes.

Voir l’article Mythe – Définir des variables de boucle dans la boucle est mauvais pour la performance pour un exemple similaire.

Vous pouvez démonter le code avec javap -c et vérifier ce que le compilateur émet réellement. Sur ma configuration (java 1.5 / mac compilé avec éclipse), le bytecode de la boucle est identique.

Le premier code est meilleur car il restreint la scope de la variable o au bloc for . Du sharepoint vue des performances, il ne peut avoir aucun effet sur Java, mais il peut avoir des compilateurs de niveau inférieur. Ils peuvent mettre la variable dans un registre si vous faites le premier.

En fait, certaines personnes pourraient penser que si le compilateur est stupide, le second extrait est plus performant. C’est ce que certains instructeurs m’ont dit au collège et je me suis moqué de lui pour cette suggestion! Fondamentalement, les compilateurs allouent de la mémoire sur la stack pour les variables locales d’une méthode une seule fois au début de la méthode (en ajustant le pointeur de la stack) et la libèrent à la fin de la méthode (en ajustant le pointeur ou il n’a pas de destructeurs à appeler). Ainsi, toutes les variables locales basées sur des stacks dans une méthode sont allouées en une fois, quel que soit leur lieu de déclaration et la quantité de mémoire requirejse. En fait, si le compilateur est stupide, il n’y a pas de différence en termes de performances, mais s’il est suffisamment intelligent, le premier code peut en fait être meilleur car il aidera le compilateur à comprendre la scope et la durée de vie de la variable! Soit dit en passant, si c’est vraiment intelligent, il ne devrait absolument pas y avoir de différence de performance car il en déduit la scope réelle.

La construction d’un object en utilisant new est totalement différente de la simple déclaration, bien sûr.

Je pense que la lisibilité est plus importante que la performance et du sharepoint vue de la lisibilité, le premier code est nettement meilleur.

Je dois admettre que je ne connais pas Java. Mais ces deux sont-ils équivalents? Les durées de vie des objects sont-elles les mêmes? Dans le premier exemple, je suppose (sans savoir java) que o seront éligibles à la récupération de la mémoire dès que la boucle se termine.

Mais dans le deuxième exemple, vous ne serez sûrement pas éligible pour le ramassage des ordures avant la sortie de la scope externe (non représentée)?

Ne pas optimiser prématurément. Mieux que l’un d’eux est:

 for(Object o : someList) { o.doSomething(); } 

parce que cela élimine le passe-partout et clarifie l’intention.

Sauf si vous travaillez sur des systèmes embarqués, auquel cas tous les paris sont désactivés. Sinon, n’essayez pas de déjouer la JVM.

J’ai toujours pensé que la plupart des compilateurs de nos jours sont assez intelligents pour faire cette dernière option. En supposant que ce soit le cas, je dirais que le premier est plus beau aussi. Si la boucle devient très volumineuse, il n’est pas nécessaire de chercher partout où o est déclaré.

Ceux-ci ont une sémantique différente. Quel est le plus significatif?

Réutiliser un object pour des “raisons de performance” est souvent faux.

La question est: qu’est-ce que l’object “signifie”? Pourquoi le créez-vous? Qu’est-ce que cela représente? Les objects doivent être parallèles aux choses du monde réel. Les choses sont créées, subissent des changements d’état et signalent leurs états pour des raisons.

Quelles sont ces raisons? Comment votre object modélise-t-il et reflète-t-il ces raisons?

Pour être au coeur de cette question … [Notez que les implémentations non-JVM peuvent faire les choses différemment si elles sont autorisées par le JLS …]

Tout d’abord, gardez à l’esprit que la variable locale “o” dans l’exemple est un pointeur, pas un object réel.

Toutes les variables locales sont allouées sur la stack d’exécution dans les emplacements de 4 octets. les doubles et les longs nécessitent deux créneaux; d’autres primitives et pointeurs en prennent un. (Même les booléens prennent un créneau complet)

Une taille de stack d’exécution fixe doit être créée pour chaque appel de méthode. Cette taille est déterminée par la variable locale maximale “slots” nécessaire à un endroit donné de la méthode.

Dans l’exemple ci-dessus, les deux versions du code requièrent le même nombre maximal de variables locales pour la méthode.

Dans les deux cas, le même bytecode sera généré, mettant à jour le même emplacement dans la stack d’exécution.

En d’autres termes, pas de pénalité de performance du tout.

Cependant, en fonction du rest du code de la méthode, la version “declaration outside the loop” peut nécessiter une allocation de stack d’exécution plus importante. Par exemple, comparez

 for (...) { Object o = ... } for (...) { Object o = ... } 

avec

 Object o; for (...) { /* loop 1 */ } for (...) { Object x =...; } 

Dans le premier exemple, les deux boucles requièrent la même allocation de stack d’exécution.

Dans le deuxième exemple, parce que “o” passe après la boucle, “x” nécessite un emplacement de stack d’exécution supplémentaire.

J’espère que ça aide, – Scott

Dans les deux cas, les informations de type pour l’object o sont déterminées au moment de la compilation. Dans le second cas, o est considéré comme global pour la boucle for et, dans le premier cas, le compilateur Java intelligent tant que la boucle dure et donc optimisera le code de telle manière qu’il n’y aura aucune respecification du type o dans chaque itération. Par conséquent, dans les deux cas, la spécification du type o sera effectuée une fois, ce qui signifie que la seule différence de performance sera dans la scope de o. De toute évidence, une scope plus étroite améliore toujours les performances, donc pour répondre à votre question: non, il n’y a pas de pénalité de performance pour le premier coup de code; En fait, ce code est plus optimisé que le second.

Dans le deuxième cas, il y a une scope inutile qui, en plus d’être un problème de performance, peut également constituer un problème de sécurité.

Le premier a beaucoup plus de sens. Il conserve la variable dans l’étendue dans laquelle il est utilisé et empêche les valeurs affectées dans une itération d’être utilisées dans une itération ultérieure. Cela est plus défensif.

On dit parfois que le premier est plus efficace, mais tout compilateur raisonnable devrait pouvoir l’optimiser pour qu’il soit exactement le même que le second.

Comme quelqu’un qui maintient plus de code que le code écrit.

La version 1 est très appréciée – il est essentiel de garder une scope aussi locale que possible pour la compréhension. Il est également plus facile de refactoriser ce type de code.

Comme discuté ci-dessus – je doute que cela fasse une différence d’efficacité. En fait, je dirais que si la scope est plus locale, un compilateur peut en faire plus!

Lorsque vous utilisez plusieurs threads (si vous faites 50+), j’ai trouvé que c’était un moyen très efficace de gérer les problèmes de threads fantômes:

 Object one; Object two; Object three; Object four; Object five; try{ for (int i=0; i 

La réponse dépend en partie de ce que fait le constructeur et de ce qu’il advient de l’object après la boucle, car cela détermine dans une large mesure comment le code est optimisé.

Si l’object est volumineux ou complexe, déclarez-le absolument en dehors de la boucle. Sinon, les personnes qui vous disent de ne pas optimiser prématurément ont raison.

J’ai en fait devant moi un code qui ressemble à ceci:

 for (int i = offset; i < offset + length; i++) { char append = (char) (data[i] & 0xFF); buffer.append(append); } ... for (int i = offset; i < offset + length; i++) { char append = (char) (data[i] & 0xFF); buffer.append(append); } ... for (int i = offset; i < offset + length; i++) { char append = (char) (data[i] & 0xFF); buffer.append(append); } 

Donc, en me basant sur les capacités du compilateur, je peux supposer qu'il y aurait une seule allocation de stack pour i et une pour append . Alors tout irait bien sauf le code dupliqué.

En outre, les applications Java sont connues pour être lentes. Je n'ai jamais essayé de faire du profilage en java, mais je suppose que les performances sont principalement issues de la gestion des allocations de mémoire.