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

StackOverflow https://stackoverflow.com/questions/256859

  •  05-07-2019
  •  | 
  •  

Question

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();
}
Était-ce utile?

La solution

À partir de la rubrique 46 de Effective Java de Joshua Bloch:

  

La boucle for-each, introduite dans   release 1.5, se débarrasse du fouillis   et la possibilité d'erreur par   masquer la variable d'itérateur ou d'index   complètement. L'idiome résultant   s'applique également aux collections et   tableaux:

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

Lorsque vous voyez les deux points (:), lisez-le comme suit:   & # 8220; in. & # 8221; Ainsi, la boucle ci-dessus se lit comme   & # 8220; pour chaque élément e dans les éléments. & # 8221; Remarque   qu'il n'y a pas de pénalité de performance   pour utiliser la boucle for-each, même pour   tableaux. En fait, cela peut offrir une légère   avantage de performance sur un ordinaire   pour boucle dans certaines circonstances, comme il   calcule la limite de l'index du tableau   juste une fois. Bien que vous puissiez le faire en   main (élément 45), les programmeurs ne le font pas   toujours le faire.

Autres conseils

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

Tout d’abord, la manière classique de parcourir en boucle la liste:

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 effectué les "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, lorsque je les testais, le troisième était le plus rapide en comptant les millisecondes sur le temps nécessaire pour chaque type de boucle avec une opération simple répétée plusieurs millions de fois - c’était Utilisation de Java 5 avec jre1.6u10 sous Windows au cas où quelqu'un vous intéresse.

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 de bouclage depuis ce que j'ai vu, réel la boucle n'est généralement pas 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 la boucle for-each de Java (certains la désignent sous le nom de boucle Iterator et d’autres comme boucle for-in ) vous êtes moins susceptible de toucher ce bogue stupide en l’utilisant. 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), il le compile simplement.

Si vous êtes intéressé par la micro-optimisation et / ou que votre logiciel utilise de nombreuses 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 le changement des boucles for que vous avez sur cet étrange, micro-optimisé.

La boucle for-each devrait généralement être préférée. Le " get " Cette approche 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 nuances de la boucle for-each .

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

Le lien affiché 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 liste   la mise en œuvre devrait mettre en œuvre 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);
     

est plus rapide que cette boucle:

for (Iterator i=list.iterator(); i.hasNext();)
      i.next();

Et pour chaque boucle utilise une version avec itérateur, donc pour ArrayList , par exemple, la boucle for-each n'est pas la plus rapide.

Malheureusement, il semble y avoir une différence.

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 de 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 déjà essayé avec Java 7 et 8 sous Windows 7.

Il est toujours préférable d'utiliser l'itérateur plutôt que l'indexation. Cela est dû au fait que l'itérateur est probablement optimzié pour l'implémentation de la liste, alors que l'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.

foreach clarifie 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 vérifier si elle fait ce que je pense que c’est le cas, par exemple. Commence-t-il à zéro, inclut-il ou exclut-il le point final, etc.?

Il semble que je passe la majeure partie de mon temps à lire du code (que j'ai écrit ou écrit par quelqu'un d'autre) et la clarté est presque toujours plus importante que les performances. Il est facile de nier les performances ces jours-ci, car Hotspot fait un travail remarquable.

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 chaque boucle).

Même avec quelque chose comme une liste de tableaux ou un vecteur, où " get " est une simple recherche de tableau, la seconde boucle a encore une surcharge supplémentaire que la première. Je m'attendrais à ce qu'il soit un peu plus lent que le premier.

Le seul moyen de savoir avec certitude est de l’évaluer, ce qui n’empêche que n’est pas aussi simple. comme cela peut paraître . Le compilateur JIT peut faire des choses très inattendues sur 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 que il y a une différence, et dans les environnements très restreints avec des listes très volumineuses, cela pourrait être une différence notable. Lors de leurs tests, la boucle pour chaque boucle a pris deux fois plus de temps. 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 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 qu'il s'agit d'une instance de java.util.ArrayList . Dans ce cas, la différence de performances serait imperceptible.

D'autre part, s'il s'agit d'une instance de java.util.LinkedList , la seconde 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.

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 simple et sûre de la programmation pour chacun, il existe des possibilités d'erreur en cas de deuxième utilisation.

C’est bizarre que personne n’ait mentionné ce qui était évident: 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 rendu).

La réponse acceptée répond à la question, en dehors du cas exceptionnel de ArrayList ...

Étant donné que la plupart des développeurs utilisent ArrayList (du moins, je le crois)

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

Directement à partir de la documentation du développeur: -

La boucle for améliorée (également appelée parfois boucle "pour-chaque") 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 d'un itérateur.

Il existe plusieurs alternatives pour effectuer une itération dans 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;
    }
}

zéro () 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 en termes de performances.

two () est le plus rapide pour les appareils sans JIT, et impossible à distinguer de 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.

Ainsi, 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.

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+" ");
        }
    }
}

Oui, pour chaque variante est plus rapide que la valeur normale basée sur l'index pour la boucle .

pour chaque variante utilise itérateur . Le parcours est donc plus rapide que la normale pour la boucle basée sur un index.
En effet, les itérateurs sont optimisés pour la traversée, car ils pointent vers juste avant l'élément suivant et juste après l'élément précédent . Une des raisons d'être basé sur l'index pour la boucle pour être lent est qu'il doit calculer et passer à la position de l'élément à chaque fois qui n'est pas avec le itérateur .

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top