Quelle est, le cas échéant, la différence de performance entre les deux boucles suivantes?
for (Object o: objectArrayList) {
o.DoSomething();
}
et
for (int i=0; i<objectArrayList.size(); i++) {
objectArrayList.get(i).DoSomething();
}
De l'article 46 dans Effective Java de Joshua Bloch:
La boucle for-each, introduite dans la version 1.5, supprime le fouillis et l’occasion d’erreur en cachant complètement la variable d’itérateur ou la variable d’index. L'idiome résultant s'applique également aux collections et aux tableaux:
// The preferred idiom for iterating over collections and arrays for (Element e : elements) { doSomething(e); }
Lorsque vous voyez les deux points (:), lisez-les comme "in". Ainsi, la boucle ci-dessus se lit comme "pour chaque élément e dans éléments". Notez qu'il n'y a aucune pénalité de performance pour l'utilisation de la boucle for-each, même pour les tableaux . En fait, elle peut offrir un léger avantage en termes de performances par rapport à une boucle for ordinaire dans certaines circonstances, car elle ne calcule la limite de l'index de tableau qu'une seule fois. Bien que vous puissiez le faire à la main (élément 45), les programmeurs ne le font pas toujours.
Toutes ces boucles font exactement la même chose, je veux juste les montrer avant de jeter mes deux sous.
Premièrement, la manière classique de parcourir en boucle List:
for(int i=0;i<strings.size();i++) { /* do something using strings.get(i) */ }
Deuxièmement, la méthode préférée car elle est moins sujette aux erreurs (combien de fois avez-vous fait la chose "oups, mélangé les variables i et j dans ces boucles dans des boucles"?).
for(String s : strings) { /* do something using s */ }
Troisièmement, le micro-optimisé pour la boucle:
int size = strings.size();
for(int i=0;++i<=size;) { /* do something using strings.get(i) */ }
Maintenant les deux centimes: Au moins quand je les testais, le troisième était le plus rapide en comptant les millisecondes sur combien de temps il a fallu pour chaque type de boucle avec une opération simple répétée quelques millions de fois - cela utilisait Java 5 avec jre1.6u10 sous Windows au cas où quelqu'un serait intéressé.
Même s’il semble au moins que le troisième soit le plus rapide, vous devriez vraiment vous demander si vous voulez prendre le risque de mettre en œuvre cette optimisation de judas partout dans votre code de bouclage puisque, d’après ce que j’ai vu, le véritable bouclage n’est plus valable. t généralement la partie la plus fastidieuse de tout programme réel (ou peut-être que je travaille simplement sur le mauvais domaine, qui sait). Et aussi, comme je l’ai mentionné sous le prétexte de Java for-each loop (certains l’appellent boucle Iterator et d’autres for- en boucle) vous l’utilisez moins lorsque vous l’utilisez. Et avant de débattre de la façon dont cela peut même être plus rapide que les autres, rappelez-vous que javac n'optimise pas du tout le bytecode (enfin, presque du tout), il le compile simplement.
Si vous êtes intéressé par la micro-optimisation et/ou que votre logiciel utilise beaucoup de boucles récursives, vous pouvez être intéressé par le troisième type de boucle. N'oubliez pas de bien analyser votre logiciel avant et après avoir changé les boucles for que vous avez en cette boucle étrange et optimisée.
La boucle for-each devrait généralement être préférée. L'approche "get" peut être plus lente si l'implémentation de la liste que vous utilisez ne prend pas en charge l'accès aléatoire. Par exemple, si une LinkedList est utilisée, vous encourez un coût de parcours, alors que l'approche pour-chaque utilise un itérateur qui garde une trace de sa position dans la liste. Plus d'informations sur le nuances de la boucle for-each .
Je pense que l'article est maintenant ici: nouvel emplacement
Le lien montré ici était mort.
L’impact sur les performances est généralement insignifiant, mais il n’est pas nul. Si vous regardez JavaDoc de l'interface RandomAccess
:
En règle générale, une implémentation de List doit implémenter cette interface si, pour des instances typiques de la classe, cette boucle:
for (int i=0, n=list.size(); i < n; i++) list.get(i);
court plus vite que cette boucle:
for (Iterator i=list.iterator(); i.hasNext();) i.next();
Et pour-chaque boucle utilise la version avec itérateur, donc pour ArrayList
par exemple, pour-chaque boucle n'est pas le plus rapide.
Il semble y avoir une différence malheureusement.
Si vous examinez le code d'octets généré pour les deux types de boucles, ils sont différents.
Voici un exemple tiré du code source Log4j.
Dans /log4j-api/src/main/Java/org/Apache/logging/log4j/MarkerManager.Java, nous avons une classe interne statique appelée Log4jMarker qui définit:
/*
* Called from add while synchronized.
*/
private static boolean contains(final Marker parent, final Marker... localParents) {
//noinspection ForLoopReplaceableByForEach
for (final Marker marker : localParents) {
if (marker == parent) {
return true;
}
}
return false;
}
Avec boucle standard:
private static boolean contains(org.Apache.logging.log4j.Marker, org.Apache.logging.log4j.Marker...);
Code:
0: iconst_0
1: istore_2
2: aload_1
3: arraylength
4: istore_3
5: iload_2
6: iload_3
7: if_icmpge 29
10: aload_1
11: iload_2
12: aaload
13: astore 4
15: aload 4
17: aload_0
18: if_acmpne 23
21: iconst_1
22: ireturn
23: iinc 2, 1
26: goto 5
29: iconst_0
30: ireturn
Avec pour chacun:
private static boolean contains(org.Apache.logging.log4j.Marker, org.Apache.logging.log4j.Marker...);
Code:
0: aload_1
1: astore_2
2: aload_2
3: arraylength
4: istore_3
5: iconst_0
6: istore 4
8: iload 4
10: iload_3
11: if_icmpge 34
14: aload_2
15: iload 4
17: aaload
18: astore 5
20: aload 5
22: aload_0
23: if_acmpne 28
26: iconst_1
27: ireturn
28: iinc 4, 1
31: goto 8
34: iconst_0
35: ireturn
Qu'est-ce qui se passe avec CET Oracle?
J'ai essayé avec Java 7 et 8 sous Windows 7.
foreach rend plus claire l'intention de votre code, ce qui est généralement préférable à une amélioration mineure de la vitesse, le cas échéant.
Chaque fois que je vois une boucle indexée, je dois l’analyser un peu plus longtemps pour s’assurer qu’elle fait ce que je pense elle le fait, par exemple. Commence-t-il à partir de zéro, inclut-il ou exclut-il le point final, etc.?
La plupart de mon temps semble être passé à lire du code (que j'ai écrit ou quelqu'un d'autre a écrit) et la clarté est presque toujours plus importante que la performance. Il est facile de nier les performances ces jours-ci car Hotspot fait un travail incroyable.
Il est toujours préférable d'utiliser l'itérateur au lieu de l'indexation. En effet, il est fort probable que l'itérateur soit optimisé pour l'implémentation de la liste, alors que l'indexé (get get) pourrait ne pas l'être. Par exemple, LinkedList est une liste, mais l'indexation à travers ses éléments sera plus lente que l'itération à l'aide de l'itérateur.
Le code suivant:
import Java.lang.reflect.Array;
import Java.util.ArrayList;
import Java.util.List;
interface Function<T> {
long perform(T parameter, long x);
}
class MyArray<T> {
T[] array;
long x;
public MyArray(int size, Class<T> type, long x) {
array = (T[]) Array.newInstance(type, size);
this.x = x;
}
public void forEach(Function<T> function) {
for (T element : array) {
x = function.perform(element, x);
}
}
}
class Compute {
int factor;
final long constant;
public Compute(int factor, long constant) {
this.factor = factor;
this.constant = constant;
}
public long compute(long parameter, long x) {
return x * factor + parameter + constant;
}
}
public class Main {
public static void main(String[] args) {
List<Long> numbers = new ArrayList<Long>(50000000);
for (int i = 0; i < 50000000; i++) {
numbers.add(i * i + 5L);
}
long x = 234553523525L;
long time = System.currentTimeMillis();
for (int i = 0; i < numbers.size(); i++) {
x += x * 7 + numbers.get(i) + 3;
}
System.out.println(System.currentTimeMillis() - time);
System.out.println(x);
x = 0;
time = System.currentTimeMillis();
for (long i : numbers) {
x += x * 7 + i + 3;
}
System.out.println(System.currentTimeMillis() - time);
System.out.println(x);
x = 0;
numbers = null;
MyArray<Long> myArray = new MyArray<Long>(50000000, Long.class, 234553523525L);
for (int i = 0; i < 50000000; i++) {
myArray.array[i] = i * i + 3L;
}
time = System.currentTimeMillis();
myArray.forEach(new Function<Long>() {
public long perform(Long parameter, long x) {
return x * 8 + parameter + 5L;
}
});
System.out.println(System.currentTimeMillis() - time);
System.out.println(myArray.x);
myArray = null;
myArray = new MyArray<Long>(50000000, Long.class, 234553523525L);
for (int i = 0; i < 50000000; i++) {
myArray.array[i] = i * i + 3L;
}
time = System.currentTimeMillis();
myArray.forEach(new Function<Long>() {
public long perform(Long parameter, long x) {
return new Compute(8, 5).compute(parameter, x);
}
});
System.out.println(System.currentTimeMillis() - time);
System.out.println(myArray.x);
}
}
Donne la sortie suivante sur mon système:
224
-699150247503735895
221
-699150247503735895
220
-699150247503735895
219
-699150247503735895
J'utilise Ubuntu 12.10 alpha avec OracleJDK 1.7 update 6.
En général, HotSpot optimise de nombreuses indirections et opérations redondantes simples. En général, vous ne devriez donc pas vous en préoccuper, sauf si elles sont nombreuses ou si elles sont fortement imbriquées.
D'autre part, la recherche indexée sur LinkedList est beaucoup plus lente que d'appeler ensuite l'itérateur suivant pour LinkedList. Vous pouvez ainsi éviter cette perte de performances tout en conservant la lisibilité lorsque vous utilisez des itérateurs (de manière explicite ou implicite dans for-each loop).
Même avec quelque chose comme ArrayList ou Vector, où "get" est une simple recherche de tableau, la seconde boucle a toujours une surcharge supplémentaire par rapport à la première. Je m'attendrais à ce que ce soit un peu plus lent que le premier.
Le seul moyen de savoir avec certitude est de le comparer, et même cela est pas aussi simple que cela puisse paraître . Le compilateur JIT peut faire des choses très inattendues dans votre code.
Voici une brève analyse de la différence mise en avant par l’équipe de développement Android:
https://www.youtube.com/watch?v=MZOf3pOAM6A
Le résultat est qu’il existe une différence et que, dans des environnements très restreints avec des listes très volumineuses, cette différence pourrait être perceptible. Lors de leurs tests, la boucle a duré deux fois plus longtemps. Cependant, leurs tests portaient sur une liste de 400 000 nombres entiers. La différence réelle par élément dans le tableau était de 6 microsecondes . Je n'ai pas testé et ils n'ont pas dit, mais je m'attendrais à ce que la différence soit légèrement plus grande en utilisant des objets plutôt que des primitives, mais même si vous construisez un code de bibliothèque où vous n'avez aucune idée de l'ampleur de ce qui vous sera demandé pour réitérer, je pense que la différence ne vaut pas la peine d'être soulignée.
Par le nom de variable objectArrayList
, je suppose que c'est une instance de Java.util.ArrayList
. Dans ce cas, la différence de performance serait imperceptible.
D'un autre côté, s'il s'agit d'une instance de Java.util.LinkedList
, La deuxième approche sera beaucoup plus lente car la List#get(int)
est une opération O(n)].
Donc, la première approche est toujours préférable, sauf si l'index est requis par la logique de la boucle.
Il est étrange que personne n'ait mentionné l'évidence - foreach alloue de la mémoire (sous la forme d'un itérateur), alors qu'une boucle for normale n'alloue aucune mémoire. Pour les jeux sur Android, c'est un problème, car cela signifie que le ramasse-miettes s'exécutera périodiquement. Dans un jeu, vous ne voulez pas que le ramasse-miettes s'exécute ... JAMAIS. Donc, n'utilisez pas de boucles foreach dans votre méthode draw (ou render).
1. for(Object o: objectArrayList){
o.DoSomthing();
}
and
2. for(int i=0; i<objectArrayList.size(); i++){
objectArrayList.get(i).DoSomthing();
}
Les deux font la même chose, mais pour une utilisation facile et sûre de la programmation pour chacun, il existe des possibilités pour une deuxième manière d’utiliser les erreurs.
La réponse acceptée répond à la question, en dehors du cas exceptionnel d'ArrayList ...
Puisque la plupart des développeurs utilisent ArrayList (au moins, je pense, oui)
Je suis donc obligé d'ajouter la bonne réponse ici.
Directement de la documentation du développeur: -
La boucle for améliorée (également appelée parfois boucle "for-each") peut être utilisée pour les collections qui implémentent l'interface Iterable et pour les tableaux. Avec les collections, un itérateur est alloué pour effectuer des appels d'interface à hasNext () et next (). Avec ArrayList, une boucle comptée écrite à la main est environ 3 fois plus rapide (avec ou sans JIT), mais pour les autres collections, la syntaxe améliorée de la boucle for sera exactement équivalente à l'utilisation explicite de l'itérateur.
Il existe plusieurs alternatives pour parcourir un tableau:
static class Foo {
int mSplat;
}
Foo[] mArray = ...
public void zero() {
int sum = 0;
for (int i = 0; i < mArray.length; ++i) {
sum += mArray[i].mSplat;
}
}
public void one() {
int sum = 0;
Foo[] localArray = mArray;
int len = localArray.length;
for (int i = 0; i < len; ++i) {
sum += localArray[i].mSplat;
}
}
public void two() {
int sum = 0;
for (Foo a : mArray) {
sum += a.mSplat;
}
}
zero () est le plus lent, car le JIT ne peut pas encore optimiser le coût d'obtention de la longueur du tableau une fois pour chaque itération de la boucle.
one () est plus rapide. Il extrait tout dans les variables locales, en évitant les recherches. Seule la longueur de la matrice offre un avantage de performance.
two () est le plus rapide pour les appareils sans JIT, et impossible à distinguer d'un () pour les appareils avec JIT. Il utilise la syntaxe améliorée de la boucle introduite dans la version 1.5 du langage de programmation Java.
Donc, vous devriez utiliser la boucle for améliorée par défaut, mais considérons une boucle comptée écrite à la main pour l'itération ArrayList critique en termes de performances.