Est-il une différence de performance entre une boucle for et une boucle for-each?

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();
}
161
demandé sur Martin Vseticka 2008-11-02 15:40:53

16 réponses

tiré de L'article 46 de Effective Java par Joshua Bloch:

la boucle pour chaque boucle, introduite en Libération 1.5, se débarrasse du désordre et la possibilité d'erreur par Cacher l'itérateur ou la variable d'indice complètement. L'résultant de l'idiome s'applique également aux recouvrements et tableaux:

// The preferred idiom for iterating over collections and arrays
for (Element e : elements) {
    doSomething(e);
}

quand vous voyez le côlon (:), lisez-le comme "dans."Ainsi, la boucle ci-dessus se lit comme "pour chaque élément e dans les éléments." Note qu'il n'y a pas de pénalité de rendement pour l'utilisation de la boucle pour-chaque, même pour tableau. En fait, il peut offrir une légère avantage de performance par rapport à un ordinaire pour boucle dans certaines circonstances, car il calcule la limite de l'index de tableau qu'une seule fois. Alors que vous pouvez le faire par main (point 45), les programmeurs ne le font pas toujours le faire.

185
répondu Vijay Dev 2008-11-02 12:47:22

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

tout D'abord, la façon classique de boucler à travers la liste:

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

Deuxièmement, la façon préférée puisque c'est moins sujet aux erreurs (combien de fois avez-vous fait la chose "oops, mixed the variables i and j in these loops within loops")?).

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 cents réels: au moins quand je testais ceux - ci, le troisième était le plus rapide en comptant millisecondes sur combien de temps il a fallu pour chaque type de boucle avec une opération simple Dans it répété quelques millions de fois-ce était en utilisant Java 5 avec jre1.6u10 sous Windows au cas où quelqu'un serait intéressé.

alors qu'il semble au moins être ainsi que le troisième est le plus rapide, vous devriez vraiment vous demander si vous voulez prendre le risque de mettre en œuvre ce Judas optimisation partout dans votre code de boucle depuis ce que j'ai vu, la boucle réelle n'est généralement pas la partie la plus chronophage de tout programme réel (ou peut-être que je travaille juste sur le mauvais champ, qui sait). Et aussi comme je l'ai mentionné dans le prétexte pour le Java pour-chaque boucle (certains l'appellent boucle Itératrice et d'autres comme pour-dans-boucle ) vous êtes moins susceptibles de frapper ce bug stupide particulier en l'utilisant. Et avant de discuter comment cela peut même être plus rapide que les autres, rappelez-vous que javac n'optimise pas le bytecode du tout (enfin, presque pas du tout de toute façon), il ne fait que le compiler.

si vous êtes dans la micro-optimisation cependant et/ou votre logiciel utilise beaucoup de boucles récursives et tel que vous pouvez être intéressé par le type de troisième boucle. N'oubliez pas de bien évaluer votre logiciel avant et après avoir changé les boucles que vous avez à cette étrange, micro-optimisé.

26
répondu P Arrayah 2014-03-07 15:00:21

la boucle pour chaque boucle doit 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 supporte pas l'accès aléatoire. Par exemple, si une liste de liens est utilisée, vous encourez un coût transversal, alors que l'approche pour-chaque utilise un itérateur qui garde la trace de sa position dans la liste. Plus d'informations sur les nuances de la boucle .

je pense que l'article est maintenant ici: nouvelles lieu

le lien montré ici était mort.

12
répondu Zach Scrivena 2018-05-07 16:57:37

Eh bien, l'impact sur le rendement est généralement insignifiant, mais il n'est pas nul. Si vous regardez JavaDoc de RandomAccess interface:

en règle générale, une Liste la mise en œuvre doit mettre en œuvre ce interface si, pour les cas 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 version avec iterator, donc pour ArrayList par exemple, pour-chaque boucle n'est pas la plus rapide.

10
répondu Sarmun 2016-01-26 08:09:46

Il semble y avoir une différence, malheureusement.

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/enregistrement/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 sur Windows 7.

5
répondu Gary Gregory 2014-09-26 20:57:58

il est toujours préférable d'utiliser l'itérateur au lieu de l'indexation. Cela est dû au fait que iterator est très probablement optimisé pour l'implémentation de la liste alors que indexed (calling get) pourrait ne pas l'être. Par exemple, LinkedList est une liste, mais l'indexation par ses éléments sera plus lente que l'itération à l'aide de l'itérateur.

4
répondu Steve Kuo 2008-11-03 03:51:34

foreach rend l'intention de votre code plus clair et qui est normalement préférable à une très légère amélioration 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'il fait ce que je pense il fait E. G. 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 le 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 rejeter la performance Ces jours - ci parce que Hotspot fait un travail incroyable.

4
répondu Fortyrunner 2008-11-03 08:22:38

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

je lance Ubuntu 12.10 alpha avec OracleJDK 1.7 update 6.

en général HotSpot optimise beaucoup d'opérations indirectes et simples, donc en général vous ne devriez pas vous inquiéter à leur sujet à moins qu'il y ait beaucoup d'entre eux en seqence ou ils sont fortement imbriqués.

d'autre part, indexé obtenir sur LinkedList est beaucoup plus lent que l'appel suivant sur iterator pour LinkedList de sorte que vous pouvez éviter que la performance hit tout en conservant la lisibilité lorsque vous utilisez des itérateurs (explicitement ou implicitement dans pour-chaque boucle).

4
répondu Wibowit 2012-08-28 21:53:42

même avec quelque chose comme un ArrayList ou un vecteur, où" get " est un simple lookup de tableau, la deuxième boucle a toujours un overhead supplémentaire que le premier n'a pas. Je m'attendais à ce qu'il soit un peu plus lent que le premier.

3
répondu Paul Tomblin 2008-11-02 12:54:21

la seule façon de savoir avec certitude est de le comparer, et même cela est pas aussi simple qu'il peut sembler . Le compilateur JIT peut faire des choses très inattendues à votre code.

3
répondu Jouni K. Seppänen 2008-11-02 13:05:43

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

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

le résultat est que est une différence, et dans des environnements très limités avec de très grandes listes, il pourrait être une différence notable. Dans leurs tests, pour chaque boucle a pris deux fois plus longtemps. Cependant, leurs tests ont été effectués sur un arraylist de 400.000 entier. 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 un peu plus grande en utilisant des objets plutôt que des primitives, mais même si vous construisez du code de bibliothèque où vous n'avez aucune idée de l'échelle de ce que vous serez invité à itérer, je pense que la différence n'est pas la peine de souligner.

3
répondu TBridges42 2016-07-02 18:12:36

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

par contre, s'il s'agit d'une instance de java.util.LinkedList , la seconde approche sera beaucoup plus lente puisque List#get(int) est une opération O(n).

ainsi la première approche est toujours préférée sauf si l'index est nécessaire par la logique dans la boucle.

2
répondu Changgeng 2013-11-06 01:33:22
1. for(Object o: objectArrayList){
    o.DoSomthing();
}
and

2. for(int i=0; i<objectArrayList.size(); i++){
    objectArrayList.get(i).DoSomthing();
}

les deux fait la même chose, mais pour l'utilisation de programmation facile et sûre pour-chacun, Il ya des possibilités d'erreur sujettes à la 2e façon d'utiliser.

1
répondu Santosh 2016-01-22 14:15:32

il est étrange que personne n'ait mentionné l'évident - foreach allocates memory (sous la forme d'un itérateur), alors qu'un normal pour boucle n'alloue aucune mémoire. Pour les jeux sur Android, c'est un problème, parce que cela signifie que le collecteur d'ordures va fonctionner périodiquement. Dans un jeu, on ne veut pas que le éboueur s'enfuie... JAMAIS. N'utilisez donc pas de boucles foreach dans votre méthode de dessin (ou de rendu).

1
répondu neural 2017-06-14 09:03:14

réponse acceptée répond à la question, à l'exception du cas exceptionnel de L'ArrayList...

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

Donc je suis obligé d'ajouter la bonne réponse ici.

directement de la documentation du développeur: -

la boucle améliorée pour (aussi parfois appelée boucle "pour chaque") peut être utilisée pour les collections qui mettent en œuvre l'interface itérative et pour les tableaux. Avec collections, un itérateur est alloué pour faire des appels d'interface à hasNext() et next(). Avec un ArrayList, une boucle comptée écrite à la main est environ 3x plus rapide (avec ou sans JIT), mais pour les autres collections, la syntaxe améliorée pour boucle sera exactement équivalente à l'utilisation explicite de l'itérateur.

il existe plusieurs alternatives pour itérer à travers 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, parce que le JIT ne peut pas encore optimiser le coût de l'obtention de la longueur du tableau une fois pour chaque itération de la boucle.

one () est plus rapide. Il tire tout dans les variables locales, en évitant les recherches. Seule la longueur du tableau offre un avantage de performance.

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

So, vous devriez utiliser la boucle améliorée par défaut, mais envisager une boucle comptée écrite à la main pour l'itération ArrayList critique.

0
répondu Sreekanth Karumanaghat 2017-08-21 05:37:44

Oui, for-each la variante est plus rapide que la normale index-based-for-loop .

for-each variante utilise iterator . Ainsi, la traversée est plus rapide que la normale "boucle 151940920" qui est basée sur l'indice.

C'est parce que iterator sont optimisés pour la traversée, car il pointe vers juste avant l'élément suivant et juste après l'élément précédent . Une des raisons pour être index-based-for-loop d'être lent est que, il doit calculer et passer à la position de l'élément à chaque fois qui n'est pas avec le iterator .

-2
répondu cse 2018-03-01 19:03:37