Y a-t-il une différence de performances entre une boucle for et une boucle for-each?

184

Quelle est, le cas échéant, la différence de performances entre les deux boucles suivantes?

for (Object o: objectArrayList) {
    o.DoSomething();
}

et

for (int i=0; i<objectArrayList.size(); i++) {
    objectArrayList.get(i).DoSomething();
}
eflles
la source
@Keparo: C'est une boucle "pour chaque" pas une boucle "for-in"
Ande Turner
en java, il s'appelle "for each", mais quand il s'agit de l'Objectif C, il s'appelle "for In" boucle.
damithH
Les performances de la boucle for étendue sont discutées ici: stackoverflow.com/questions/12155987/…
eckes
Même s'il y avait une différence, elle serait si mineure que vous devriez privilégier la lisibilité à moins que ce morceau de code ne soit exécuté des milliards de fois par seconde. Et puis, vous auriez besoin d'une référence appropriée pour éviter de toute façon une optimisation prématurée.
Zabuzard

Réponses:

212

À partir de l'article 46 en Java efficace par Joshua Bloch:

La boucle for-each, introduite dans la version 1.5, élimine l'encombrement et la possibilité d'erreur en masquant complètement l'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 le signe deux-points (:), lisez-le comme "in". Ainsi, la boucle ci-dessus se lit comme «pour chaque élément e dans les éléments». Notez qu'il n'y a pas de pénalité de performances pour l'utilisation de la boucle for-each, même pour les tableaux. En fait, il peut offrir un léger avantage de performances par rapport à une boucle for ordinaire dans certaines circonstances, car il ne calcule la limite de l'index du tableau qu'une seule fois. Bien que vous puissiez le faire à la main (Point 45), les programmeurs ne le font pas toujours.

Vijay Dev
la source
48
Il convient de mentionner que dans une boucle for-each, il n'y a aucun moyen d'accéder à un compteur d'index (car il n'existe pas)
basszero
Oui, mais ce compteur est maintenant visible en dehors de la boucle. Bien sûr, c'est une solution simple, mais il en va de même pour chacun!
Indolering
74
Il y a la pénalité de performance de l'allocation de l'itérateur. J'avais du code hautement parallèle dans un fond d'écran animé Android. J'ai vu que le ramasse-miettes devenait fou. C'était parce que les boucles for-each allouaient des itérateurs temporaires dans de nombreux threads différents (de courte durée), ce qui causait beaucoup de travail au garbage collector. Le passage à des boucles basées sur des index réguliers a résolu le problème.
gsingh2011
1
@ gsingh2011 Mais cela dépend aussi si vous utilisez une liste d'accès aléatoire ou non. Utiliser un accès basé sur un index à des listes d'accès non aléatoire sera bien pire que d'utiliser for-each avec des listes d'accès aléatoire, je suppose. Si vous travaillez avec l'interface List et que vous ne connaissez donc pas le type d'implémentation réel, vous pouvez vérifier si la liste est une instance de (implémente) RandomAccess, si cela vous tient vraiment à cœur
Puce
3
@ gsingh2011 Les documents Android ( developer.android.com/training/articles/perf-tips.html#Loops ) mentionnent que vous aurez une pénalité de performances lorsque vous utilisez foreach sur une ArrayList uniquement, pas sur d'autres collections. Je suis curieux de savoir si c'était votre cas.
Viccari
29

Toutes ces boucles font exactement la même chose, je veux juste les montrer avant de jeter mes deux cents.

Tout d'abord, la manière classique de parcourir 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 le truc "oups, mélangé les variables i et j dans ces boucles dans des boucles"?).

for (String s : strings) { /* do something using s */ }

Troisièmement, la boucle micro-optimisée pour:

int size = strings.size();
for (int i = -1; ++i < size;) { /* do something using strings.get(i) */ }

Maintenant, les deux cents réels: au moins lorsque je les testais, le troisième était le plus rapide en comptant les millisecondes sur le temps qu'il fallait pour chaque type de boucle avec une simple opération répétée plusieurs millions de fois - cela utilisait Java 5 avec jre1.6u10 sur Windows au cas où quelqu'un serait intéressé.

Bien qu'il semble au moins que le troisième soit le plus rapide, vous devriez vraiment vous demander si vous voulez prendre le risque d'implémenter cette optimisation de judas partout dans votre code en boucle puisque d'après ce que j'ai vu, la boucle réelle n'est pas C'est généralement la partie la plus longue de tout programme réel (ou peut-être que je travaille juste sur le mauvais domaine, qui sait). Et aussi comme je l'ai mentionné dans le prétexte de la boucle Java for-each (certains y font référence en tant que boucle Iterator et d'autres en tant que boucle for-in ), vous êtes moins susceptible de rencontrer ce bogue stupide particulier lors de son utilisation. Et avant de débattre du fait que 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 de toute façon), il le compile simplement.

Si vous êtes dans la micro-optimisation et / ou si votre logiciel utilise beaucoup de boucles récursives, vous pourriez être intéressé par le troisième type de boucle. N'oubliez pas de bien comparer votre logiciel avant et après le changement des boucles for que vous avez avec cette étrange boucle micro-optimisée.

P Arrayah
la source
5
Veuillez noter que la boucle for avec ++ i <= size est "basée sur 1", par exemple la méthode get à l'intérieur de la boucle sera appelée pour les valeurs 1, 2, 3, etc.
volley
15
Une meilleure façon d'écrire la boucle micro-optimisée est pour (int i = 0, size = strings.size (); ++ i <= size;) {} Ceci est préférable car cela minimise la portée de la taille
Dónal
1
le troisième ne commence-t-il pas à partir de i = 1 la première fois qu'il parcourt la boucle, en sautant le premier élément. et ce n'est pas une boucle for. int n = chaînes.longueur; while (n -> 0) {System.out.println ("" + n + "" + chaînes [n]); }
Lassi Kinnunen
1
@ Dónal ce motif de boucle manque le premier et donne un IOOBE. Celui-ci fonctionne: for (int i = -1, size = list.size (); ++ i <size;)
Nathan Adams
1
"Toutes ces boucles font exactement la même chose" est incorrect. L'un utilise un accès aléatoire get(int), l'autre utilise un fichier Iterator. Considérez LinkedListoù les performances de for(int i=0;i<strings.size();i++) { /* do something using strings.get(i) */ }sont bien pires car elles le font get(int)n fois.
Steve Kuo
13

La boucle for-each devrait généralement être préférée. L'approche "get" peut être plus lente si l'implémentation List 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 traversée, tandis que l'approche for-each utilise un itérateur qui garde une trace de sa position dans la liste. Plus d'informations sur les nuances de la boucle for-each .

Je pense que l'article est maintenant là: nouvel emplacement

Le lien montré ici était mort.

Zach Scrivena
la source
12

Eh bien, l'impact sur les performances est généralement insignifiant, mais n'est pas nul. Si vous regardez JavaDoc de l' RandomAccessinterface:

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);

s'exécute 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 ArrayListpar exemple, pour chaque boucle n'est pas la plus rapide.

Sarmun
la source
Vraiment chacun? Même un avec un tableau? J'ai lu ici stackoverflow.com/questions/1006395/… qu'il n'implique pas d'itérateur.
Ondra Žižka
@ OndraŽižka: La boucle for-each utilise des itérateurs lors du bouclage sur Iterables, mais pas sur des tableaux. Pour les tableaux, une boucle for avec une variable d'index est utilisée. Il y a des informations à ce sujet dans le JLS .
Lii
oui, vous devez d'abord le créer dans un tableau avec toArray, ce qui a un coût.
Lassi Kinnunen
6

Il semble y avoir malheureusement une différence.

Si vous regardez le code d'octets généré pour les deux types de boucles, ils sont différents.

Voici un exemple 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 for-each:

  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

Que se passe-t-il avec cet Oracle?

J'ai essayé cela avec Java 7 et 8 sur Windows 7.

Gary Gregory
la source
7
Pour ceux qui essaient de lire le désassemblage, le résultat net est que le code généré à l'intérieur de la boucle est identique, mais la configuration for-each semble avoir créé une variable temporaire supplémentaire contenant une référence au deuxième argument. Si la variable cachée supplémentaire est enregistrée, mais que le paramètre lui-même ne l'est pas pendant la génération du code, alors le for-each serait plus rapide; si le paramètre est enregistré dans l'exemple for (;;), le temps d'exécution serait identique. Vous avez une référence?
Robin Davies
4

Il est toujours préférable d'utiliser l'itérateur au lieu de l'indexation. Cela est dû au fait que l'itérateur est très probablement optimisé pour l'implémentation de List alors qu'indexé (appelant get) peut 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.

Steve Kuo
la source
10
Je pense qu'il n'y a pas de "toujours" dans les optimisations de performances.)
eckes
4

foreach rend l'intention de votre code plus claire et cela est normalement préféré à une amélioration très 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 m'assurer qu'elle fait ce que je pense , par exemple, est-ce qu'elle commence à zéro, inclut-elle ou exclut le point final, etc.?

La plupart de mon temps semble être consacré à la lecture de code (que j'ai écrit ou que quelqu'un d'autre a écrit) et la clarté est presque toujours plus importante que la performance. Il est facile de rejeter les performances de nos jours car Hotspot fait un travail incroyable.

Fortyrunner
la source
4

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 mise à jour 6.

En général, HotSpot optimise beaucoup d'indirections et d'opérations redondantes simples, donc en général, vous ne devriez pas vous en soucier à moins qu'il y en ait beaucoup dans la séquence ou qu'elles soient fortement imbriquées.

D'autre part, obtenir indexé sur LinkedList est beaucoup plus lent que d'appeler next on iterator pour LinkedList afin que vous puissiez éviter ce coup de performance tout en conservant la lisibilité lorsque vous utilisez des itérateurs (explicitement ou implicitement dans la boucle for-each).

Wibowit
la source
3

Même avec quelque chose comme ArrayList ou Vector, où "get" est une simple recherche de tableau, la deuxième boucle a toujours une surcharge supplémentaire que la première n'a pas. Je m'attendrais à ce que ce soit un peu plus lent que le premier.

Paul Tomblin
la source
La première boucle doit également obtenir chaque élément. Il crée un itérateur dans les coulisses pour ce faire. Ils sont vraiment équivalents.
Bill the Lizard
En pensant en termes de C, un itérateur peut simplement incrémenter un pointeur, mais un get devrait multiplier la valeur de i par la largeur d'un pointeur à chaque fois.
Paul Tomblin
Cela dépend du type de liste que vous utilisez. Je pense que vous avez raison cependant, utiliser get ne serait jamais plus rapide et parfois plus lent.
Bill the Lizard
3

Le seul moyen de le savoir avec certitude est de le comparer, et même cela n'est pas aussi simple que cela puisse paraître . Le compilateur JIT peut faire des choses très inattendues à votre code.

Jouni K. Seppänen
la source
3

Voici une brève analyse de la différence mise en évidence par l'équipe de développement Android:

https://www.youtube.com/watch?v=MZOf3pOAM6A

Le résultat est qu'il ya est une différence, et dans des environnements très restreints avec des listes très importantes , il pourrait être une différence notable. Lors de leurs tests, le pour chaque boucle a pris deux fois plus de temps. Cependant, leurs tests étaient sur une arraylist de 400 000 entiers. La différence réelle par élément du tableau était de 6 microsecondes . Je n'ai pas testé et ils ne l'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 toujours à moins que vous ne construisiez un code de bibliothèque où vous n'avez aucune idée de l'échelle de ce qui vous sera demandé pour répéter, je pense que la différence ne vaut pas la peine d'être soulignée.

TBridges42
la source
2

Par le nom de la variable objectArrayList, je suppose que c'est une instance dejava.util.ArrayList . Dans ce cas, la différence de performance serait imperceptible.

En revanche, s'il s'agit d'une instance de java.util.LinkedList, la seconde approche sera beaucoup plus lente car laList#get(int) s'agit d'une opération O (n).

Ainsi, la première approche est toujours préférée à moins que l'index ne soit requis par la logique de la boucle.

Changgeng
la source
1
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 de programmation facile et sûre pour chacun, il existe des possibilités d'erreur dans la deuxième façon d'utiliser.

Santosh
la source
1

C'est bizarre 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 fonctionnera périodiquement. Dans un jeu, vous ne voulez pas que le ramasse-miettes s'exécute ... JAMAIS. N'utilisez donc pas de boucles foreach dans votre méthode de dessin (ou de rendu).

neural
la source
1

La réponse acceptée répond à la question, mis à part le cas exceptionnel d'ArrayList ...

Puisque la plupart des développeurs s'appuient sur ArrayList (du moins je le crois)

Je suis donc obligé d'ajouter la bonne réponse ici.

Directement depuis la documentation développeur: -

La boucle for améliorée (parfois également appelée 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 une ArrayList, une boucle comptée manuscrite est environ 3 fois plus rapide (avec ou sans JIT), mais pour d'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 dans la boucle.

one () est plus rapide. Il extrait tout dans des variables locales, évitant les recherches. Seule la longueur de la baie offre un avantage en termes de performances.

two () est le plus rapide pour les appareils sans JIT, et impossible à distinguer de one () pour les appareils avec un JIT. Il utilise la syntaxe de boucle for améliorée introduite dans la version 1.5 du langage de programmation Java.

Par conséquent, vous devez utiliser la boucle for améliorée par défaut, mais envisagez une boucle comptée manuscrite pour l'itération ArrayList critique en termes de performances.

Sreekanth Karumanaghat
la source
-2

Oui, la for-eachvariante est plus rapide que la normaleindex-based-for-loop .

for-eachutilisations de variantes iterator. La traversée est donc plus rapide que la forboucle normale basée sur un index.
En effet, ils iteratorsont optimisés pour la traversée, car il pointe juste avant l'élément suivant et juste après l'élément précédent . Une des raisons index-based-for-loopd'être lent est que, il doit calculer et se déplacer à chaque fois à la position de l'élément qui n'est pas avec le iterator.

cse
la source
-3
public class FirstJavaProgram {

    public static void main(String[] args) 
    {
        int a[]={1,2,3,45,6,6};

// Method 1: this is simple way to print array 

        for(int i=0;i<a.length;i++) 
        { 
            System.out.print(a[i]+" ");
        }

// Method 2: Enhanced For loop

        for(int i:a)
        {
            System.out.print(i+" ");
        }
    }
}
SS Tiwari
la source