Java 5 nous a donné les boucles for-each, qui devraient être utilisées là où c'est possible.Quel idiome pour itérer un tableau est le plus efficace?
Mais quel est l'idiome le plus efficace si vous avez besoin d'utiliser l'index du tableau dans le bloc?
// Option (1)
for (int i = array.length - 1; i >= 0; i--) {
// Code.
}
// Option (2)
for (int i = 0; i < array.length; i++) {
// Code.
}
// Option (3)
for (int i = 0, n = array.length; i < n; i++) {
// Code.
}
(De toute évidence, cela ne fera pas une grande différence de performance dans la plupart des programmes, mais l'humour moi.) :-)
boucles en arrière et est hideux. Peut-être même cache hostile? Ou le transformateur moderne peut-il détecter la marche arrière dans la mémoire?
est plus courte et je peux voir comment le compilateur JIT peut comprendre que
array
ne change jamais et donclength
est constante, de sorte qu'il peut essentiellement le remplacer par (3). Mais fait-il cela? (Supposons que la machine virtuelle Java est Hotspot/Java Oracle 7.)est suggérée par Joshua Bloch au point 45 Effective Java comme l'option la plus rapide si elle est une
Collection.size()
qui est la limite supérieure. Mais s'applique-t-il également aux tableaux? Du bytecode (voir ci-dessous) je peux voir celui-là enregistrer une instructionarraylength
par cycle (pré-optimisé).
This question au sujet des boucles dans la machine virtuelle Dalvik, listes (1) - (3) le plus rapide au plus lent. Cependant, l'information date de 2008 et Dalvik est beaucoup plus mature aujourd'hui, donc je ne pense pas que ce soit toujours le cas.
En regardant le bytecode généré à partir des exemples ci-dessus, il existe des différences évidentes:
Compiled from "ForLoops.java"
public class ForLoops extends java.lang.Object{
static int[] array;
public ForLoops();
Code:
0: aload_0
1: invokespecial #10; //Method java/lang/Object."<init>":()V
4: return
public static void forLoop1();
Code:
0: getstatic #17; //Field array:[I
3: arraylength
4: iconst_1
5: isub
6: istore_0
7: goto 13
10: iinc 0, -1
13: iload_0
14: ifge 10
17: return
public static void forLoop2();
Code:
0: iconst_0
1: istore_0
2: goto 8
5: iinc 0, 1
8: iload_0
9: getstatic #17; //Field array:[I
12: arraylength
13: if_icmplt 5
16: return
public static void forLoop3();
Code:
0: iconst_0
1: istore_0
2: getstatic #17; //Field array:[I
5: arraylength
6: istore_1
7: goto 13
10: iinc 0, 1
13: iload_0
14: iload_1
15: if_icmplt 10
18: return
}
En théorie, 1 et 3 sont plus efficaces que 2, dans le cas général, puisque 'array.length' n'a besoin d'être évalué qu'une seule fois. Cependant, l'évaluation de 'longueur 'en Java est assez simple, surtout si JITCed, et ainsi la différence théorique disparaît pratiquement dans la pratique. Très probablement, cela dépend de ce qui se trouve dans le corps de la boucle et comment l'optimiseur JITC fusionne le contrôle de boucle avec le corps de la boucle. –
@Nicholas Beau travail tirant le bytecode! Mais il est important de garder à l'esprit que le bytecode n'est finalement pas ce qui est exécuté en raison du JIT. Si nous pouvions obtenir des instructions JITed, cela * serait * la réponse finale, mais c'est probablement irréaliste car (a) il est spécifique à la plateforme, et (b) il y a différents "niveaux" de JITing. De plus, je ne connais pas de JIT qui donne de la transparence sur le code généré pendant l'exécution, du moins pas sans fouiller vous-même dans l'espace mémoire de la JVM. – sigpwned
Il va sans dire que le choix "le plus efficace" est généralement celui qui correspond le mieux au reste du code. Par exemple, itérer à l'envers est parfois gênant et parfois très utile. De même, vérifier 'array.length' à chaque itération peut être requis, si l'objet tableau est remplacé dans la boucle. –