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, dans le code octet, ces deux méthodes sont totalement équivalentes, la première méthode est évidemment meilleure en termes de style, mais je veux m'assurer que c'est bien le cas.
Dans les compilateurs d'aujourd'hui, non. Je déclare les objets dans la plus petite portée possible, parce que 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.
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 non. Je voudrais (deviner} _ cela produira un code identique.
Comparez cela avec le nombre d'erreurs que vous éviterez probablement en analysant correctement votre variable à l'aide de la déclaration in-loop ...
Il n’existe aucune pénalité en termes de performances pour la déclaration de l’objet o dans la boucle. Le compilateur génère un code intermédiaire très similaire et procède aux optimisations appropriées.
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ésassembler le code avec javap -c et vérifier ce que le compilateur émet réellement. Sur ma configuration (Java 1.5/mac compilé avec Eclipse), le bytecode de la boucle est identique.
Le premier code est préférable car il limite la portée de la variable o
au bloc for
. Du point de vue des performances, cela n’aura peut-être aucun effet en Java, mais dans les compilateurs de bas niveau. Ils pourraient mettre la variable dans un registre si vous faites le premier.
En fait, certaines personnes pourraient penser que si le compilateur est stupide, le deuxième extrait de code est meilleur en termes de performances. C'est ce qu'un instructeur m'a dit au collège et je me suis moqué de lui pour cette suggestion! Fondamentalement, les compilateurs allouent de la mémoire sur la pile pour les variables locales d'une méthode au début de la méthode (en ajustant le pointeur de la pile) et la libèrent à la fin de la méthode (en ajustant à nouveau le pointeur de la pile, en supposant que ce n'est pas C++. ou il n'a pas de destructeurs à appeler). Ainsi, toutes les variables locales basées sur une pile dans une méthode sont allouées en même temps, peu importe où elles sont déclarées et combien de mémoire elles ont besoin. En fait, si le compilateur est stupide, il n'y a pas de différence en termes de performances, mais s'il est assez intelligent, le premier code peut en fait être meilleur car cela aidera le compilateur à comprendre la portée et la durée de vie de la variable! À propos, si c'est vraiment intelligent, il ne devrait absolument pas y avoir de différence de performances car cela en déduit la portée réelle.
La construction d'un objet 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 point de vue de la lisibilité, le premier code est nettement meilleur.
Ne pas optimiser prématurément. Mieux que l'un ou l'autre est:
for(Object o : someList) {
o.doSomething();
}
parce qu'il élimine les problèmes et clarifie l'intention.
Sauf si vous travaillez sur des systèmes embarqués, dans ce cas, tous les paris sont désactivés. Sinon, n'essayez pas de déjouer la JVM.
Je dois admettre que je ne connais pas Java. Mais ces deux équivalents? Les durées de vie des objets sont-elles les mêmes? Dans le premier exemple, je suppose (sans connaître Java) que o sera éligible pour le nettoyage de la mémoire dès la fin de la boucle.
Mais dans le deuxième exemple, sûrement, ne sera-t-il pas éligible pour la collecte des ordures tant que la portée extérieure (non illustrée) n'est sortie?
J'ai toujours pensé que la plupart des compilateurs sont assez intelligents pour utiliser cette dernière option. En supposant que ce soit le cas, je dirais que le premier semble également plus joli. Si la boucle devient très grande, il n'est pas nécessaire de chercher partout où o est déclaré.
Ceux-ci ont une sémantique différente. Lequel est le plus significatif?
Réutiliser un objet pour des "raisons de performance" est souvent une erreur.
La question est qu'est-ce que l'objet "signifie"? POURQUOI le créez-vous? Qu'est-ce que cela représente? Les objets doivent être parallèles aux choses du monde réel. Les choses sont créées, subissent des changements d'état et rapportent leurs états pour des raisons.
Quelles sont ces raisons? Comment votre objet modèle-t-il et reflète-t-il ces raisons?
Pour aller au cœur de cette question ... [Notez que les implémentations non-JVM peuvent faire les choses différemment si le JLS le permet ...]
Tout d'abord, gardez à l'esprit que la variable locale "o" dans l'exemple est un pointeur, pas un objet réel.
Toutes les variables locales sont allouées sur la pile d'exécution dans des emplacements de 4 octets. les doubles et les longs nécessitent deux créneaux horaires; les autres primitives et les pointeurs en prennent un. (Même les booléens prennent une fente complète)
Une taille de pile 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 "emplacements" 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 pile d'exécution.
En d'autres termes, aucune pénalité de performance.
CEPENDANT, selon le reste du code de la méthode, la version "déclaration en dehors de la boucle" peut nécessiter une allocation de pile 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 nécessitent la même allocation de pile d'exécution.
Dans le deuxième exemple, étant donné que "o" vit au-delà de la boucle, "x" nécessite un emplacement de pile d'exécution supplémentaire.
J'espère que cela vous aidera, - Scott
Dans les deux cas, le type d’information pour l’objet o est déterminé lors de la compilation. Dans la seconde instance, o est considéré comme étant global à la boucle for et, dans un premier temps, l’habile compilateur Java sait que o devra être disponible pour tant que la boucle dure et optimisera donc le code de manière à ce qu'il n'y ait aucune re-spécification du type de o à chaque itération. Par conséquent, dans les deux cas, la spécification du type de o sera effectuée une seule fois, ce qui signifie que la seule différence de performances serait celle de o. De toute évidence, une portée plus étroite améliore toujours les performances. Par conséquent, pour répondre à votre question, non, il n'y a aucune pénalité de performance pour le premier snip de code; en réalité, cette capture de code est plus optimisée que la seconde.
Dans la seconde partie, o se voit accorder une portée inutile qui, en plus d’être un problème de performances, peut également être un problème de sécurité.
En tant que personne qui gère plus de code que d'écrire du code.
La version 1 est de loin préférée - garder une portée aussi locale que possible est essentiel à la compréhension. Il est également plus facile de reformuler 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 portée est plus locale, un compilateur peut en faire plus!
Lors de l'utilisation de plusieurs threads (si vous faites plus de 50 ans), j'ai trouvé que c'était un moyen très efficace de gérer les problèmes de thread fantôme:
Object one;
Object two;
Object three;
Object four;
Object five;
try{
for (int i=0; i<someValue; i++)
{
o = someList.get(i);
o.doSomething;
}
}catch(e){
e.printstacktrace
}
finally{
one = null;
two = null;
three = null;
four = null;
five = null;
System.gc();
}
Le premier est beaucoup plus logique. Il conserve la variable dans la portée dans laquelle il est utilisé et empêche que les valeurs attribuées dans une itération ne soient utilisées lors d'une itération ultérieure, car elles sont plus défensives.
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 dernier.