pour l'optimisation de boucle

List<String> flowers = new ArrayList<String>();

Ma boucle for ressemble actuellement à ceci...

for (int i = 0; i < flowers.size(); i++) {
...
}

Ou devrais-je changer cela pour ressembler au code donné ci-dessous

int size = flowers.size();
for (int i = 0; i < size; i++) {
...
}

Qui est plus performant (en supposant que j'ai un large éventail de fleurs), je suppose que ce devrait être le dernier.

44
demandé sur Andreas_D 2011-05-23 10:22:34

15 réponses

Il est préférable d'utiliser de boucle for-each [plus lisible]

for (Flower flower :flowers){
    //...
}

J'ai vidé des instructions en utilisant javap pour le code suivant:

public void forLoop1() {
    List<String> lst = new ArrayList<String>();
    for (int i = 0; i < lst.size(); i++) {
        System.out.println("hi");
    }
}

public void forLoop2() {
    List<String> lst = new ArrayList<String>();
    int size = lst.size();
    for (int i = 0; i < size; i++) {
        System.out.println("hi");
    }
}

public void forLoop1();
  Code:
   0:   new     #2; //class java/util/ArrayList
   3:   dup
   4:   invokespecial   #3; //Method java/util/ArrayList."<init>":()V
   7:   astore_1
   8:   iconst_0
   9:   istore_2
   10:  iload_2
   11:  aload_1
   12:  invokeinterface #4,  1; //InterfaceMethod java/util/List.size:()I
   17:  if_icmpge       34
   20:  getstatic       #5; //Field java/lang/System.out:Ljava/io/PrintStream;
   23:  ldc     #6; //String hi
   25:  invokevirtual   #7; //Method java/io/PrintStream.println:(Ljava/lang/Str
ing;)V
   28:  iinc    2, 1
   31:  goto    10
   34:  return

public void forLoop2();
  Code:
   0:   new     #2; //class java/util/ArrayList
   3:   dup
   4:   invokespecial   #3; //Method java/util/ArrayList."<init>":()V
   7:   astore_1
   8:   aload_1
   9:   invokeinterface #4,  1; //InterfaceMethod java/util/List.size:()I
   14:  istore_2
   15:  iconst_0
   16:  istore_3
   17:  iload_3
   18:  iload_2
   19:  if_icmpge       36
   22:  getstatic       #5; //Field java/lang/System.out:Ljava/io/PrintStream;
   25:  ldc     #6; //String hi
   27:  invokevirtual   #7; //Method java/io/PrintStream.println:(Ljava/lang/Str
ing;)V
   30:  iinc    3, 1
   33:  goto    17
   36:  return

Ça n'optimise pas pour moi.

Version Java "1.6.0_22" Java (TM) SE Environnement d'exécution (build 1.6.0_22-B04) Java HotSpot(TM) Client VM (construire 17.1-b03, mode mixte, le partage)

Donc, si vous avez besoin de choisir parmi les deux mentionnés, optez pour la seconde, mais personnellement, je le ferais allez pour for-each.


Pour chaque Performance

De L'article 46 dans Effective Java {[29] } par Joshua Bloch:

La boucle for-each, introduite dans version 1.5, se débarrasse de l'encombrement et la possibilité d'erreur par masquer l'itérateur ou la variable d'index complètement. L'résultant de l'idiome s'applique également aux collections 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 performance pour utiliser la boucle for-each, même pour tableau. En fait, il 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 qu'une seule fois. Alors que vous pouvez le faire par main (article 45), les programmeurs ne le font pas toujours le faire.


Voir Aussi

56
répondu Jigar Joshi 2017-05-23 11:47:26

Désolé de le dire, mais la réponse de @Jigar est incorrecte. C'est la réponse correcte. (tldr; ne pas utiliser for : each).

import java.util.ArrayList;
import java.util.List;

public class LoopTest {

    public static void main(String s[]) {

        long start, end;

        List<Integer> a =  new ArrayList<Integer>();

        for (int i = 0; i < 2500000; i++) {
            a.add(i);
        }

        ///// TESTING FOR : EACH LOOP

        start = System.currentTimeMillis();

        for (Integer j : a) {
            int x = j + 3;
        }

        end = System.currentTimeMillis();

        System.out.println(end - start
                + " milli seconds for [ Integer j : a ] ");

        ////// TESTING DEFAULT LOOP

        start = System.currentTimeMillis();
        for (int i = 0; i < a.size(); i++) {
            int x = a.get(i) + 3;
        }

        end = System.currentTimeMillis();

        System.out.println(end - start
                + " milli seconds for [ int i = 0; i < a.length; i++ ] ");


        ////// TESTING SLIGHTLY OPTIMIZED LOOP

        start = System.currentTimeMillis();
        int size = a.size();
        for (int i = 0; i < size; i++) {
            int x = a.get(i) + 3;
        }

        end = System.currentTimeMillis();

        System.out.println(end - start
                + " milli seconds for [ int i = 0; i < size; i++ ] ");        

        //// TESTING MORE OPTIMIZED LOOP

        start = System.currentTimeMillis();
        for (int i = size; --i >= 0;) {
            int x = a.get(i) + 3;
        }

        end = System.currentTimeMillis();

        System.out.println(end - start
                + " milli seconds for [ int i = size; --i >= 0; ] ");       

    }

}

Les résultats:

96 milli seconds for [ Integer j : a ] 
57 milli seconds for [ int i = 0; i < a.length; i++ ] 
31 milli seconds for [ int i = 0; i < size; i++ ] 
31 milli seconds for [ int i = size; --i >= 0; ] 

Vous pouvez vous décider, mais trop d'attribution est donnée à l'optimiseur JVM. Vous devez toujours être intelligent avec votre propre code, et utiliser la notation for : each N'est pas une bonne idée (presque jamais). Comme vous pouvez le voir, vous avez une bonne idée en mettant la taille de sa variable.

Même si certaines de ces optimisations peuvent dépendre de la JVM (et certains peuvent entrer dans le JIT), il est important de savoir ce que Java fait et ce que Java ne fait pas.

20
répondu David Titarenco 2011-05-23 06:59:05

La JVM ne peut pas l'optimiser car size() est une méthode et la JVM ne peut pas (et n'essaiera pas de) déterminer que size() retournera toujours la même valeur dans ce contexte. Si la valeur size() ne change pas, la seconde est légèrement plus performante, mais le gain est si, si léger que vous n'avez même pas à envisager de l'utiliser.

11
répondu Raze 2013-02-25 22:04:24

Si les performances sont critiques, utilisez la boucle de compteur simple, mais pour 98% des cas, la clarté et la simplicité du code sont beaucoup plus importantes (comme 1000x ou plus) et vous devriez utiliser une boucle for-each.

@David souligne que l'utilisation d'un compteur est plus rapide, mais je voudrais souligner que même pour 10 000 entrées, la différence est inférieure à la microseconde.

Si vous avez une collection de plus de 10 000 entrées, il est fort probable que vous ne devriez pas itérer par force brute sur toutes les possibilités. Il est plus probable une collection avec une recherche comme une Carte est une meilleure solution pour ce que vous avez à l'esprit. Si vous avez beaucoup moins de 10 000 entrées, les performances sont moins susceptibles d'être importantes.

9
répondu Peter Lawrey 2011-05-23 07:53:08

Le comportement est différent si la liste du tableau change lors de l'itération. Mais je suppose que vous ne le faites pas. Selon mon test, ce dernier est généralement plus rapide (spécialement sur des systèmes comme Android). Je l'écrirais comme suit:

for (int i = 0, size = flowers.size(); i < size; i++) {
    ...
}
6
répondu Thomas Mueller 2011-05-23 07:07:42

À partir de la spécification du langage Java (14.14.1):

L'instruction basic for exécute un code d'initialisation, exécute ensuite un Expression , une instruction et un code de mise à jour à plusieurs reprises jusqu'à ce que la valeur de L'Expression est fausse.

L'Expression est i < flowers.size() dans votre premier exemple et elle est évaluée une fois à chaque itération. Dans votre cas particulier, cela ne devrait pas faire de différence notable, car flowers.getSize() sur ArrayList est une méthode très courte. Mais, en général, si le résultat de l'expression est le même pour chaque itération et coûteux, alors faites un pré calcul.

Conséquence : cela doit produire la même sortie dans chaque implémentation D'une machine virtuelle Java et prouve que Expression est évaluée une fois à chaque itération:

int counter2 = 10;
for (int counter1 = 0; counter1 < counter2; counter1++) {
  System.out.println(counter1 + ", " + counter2);
  counter2--;
}

Sortie:

0, 10
1, 9
2, 8
3, 7
4, 6
4
répondu Andreas_D 2011-05-23 06:52:16

La meilleure option est

[ int i = 0; i < size; i++ ]

Vos résultats varieront en fonction de la JVM et d'autres paramètres tels que-client vs-server parce que certaines des mesures sont si minuscules que vous devez utiliser NANO secondes pour mesurer, et vous devez faire de nombreux tests sinon vous vous retrouvez avec le GC jouer avec les résultats. De plus, ces types de tests ont l'habitude que la JVM optimise la boucle à rien. J'ai essayé d'éliminer ce risque en mettant la variable qu'il modifie à la fin du code du écran.

1.6
-server
7.968242071 milli seconds for [ Integer j : a ] 
7.206275775999999 milli seconds for [ int i = 0; i < a.length; i++ ]  
1.5864E-5 milli seconds for [ int i = 0; i < size; i++ ] 
14.774186076999998 milli seconds for [ int i = size; --i >= 0; ] 

-client
83.36101683999999 milli seconds for [ Integer j : a ] 
44.288568631 milli seconds for [ int i = 0; i < a.length; i++ ]  
2.3191E-5 milli seconds for [ int i = 0; i < size; i++ ] 
24.826621246 milli seconds for [ int i = size; --i >= 0; ] 

1.7

-server
7.029150422 milli seconds for [ Integer j : a ] 
6.6269827779999995 milli seconds for [ int i = 0; i < a.length; i++ ]  
1.3852E-5 milli seconds for [ int i = 0; i < size; i++ ] 
13.842110377 milli seconds for [ int i = size; --i >= 0; ] 
13.868426141 milli seconds for [ int i = a.size()-1; i >= 0; i-- ] 
1.6618000000000003E-5 milli seconds for [ int i = 0; i < a.size(); i++ ] 

-client
7.382479727 milli seconds for [ Integer j : a ] 
6.748068759 milli seconds for [ int i = 0; i < a.length; i++ ]  
1.4162999999999998E-5 milli seconds for [ int i = 0; i < size; i++ ] 
13.951547335999999 milli seconds for [ int i = size; --i >= 0; ] 
13.929234053999998 milli seconds for [ int i = a.size()-1; i >= 0; i-- ] 
1.6873E-5 milli seconds for [ int i = 0; i < a.size(); i++ ] 

Code D'essai:

public static void main(String s[]) {
long start=0, end = 0, delta = 0;
//int[] a = new int[2500000];
List<Integer> a = new ArrayList<Integer>();
int x = 0;

for (int i = 0; i < 2500000; i++) {
    a.add(i);
}

start=0; end = 0; delta = 0;
for (int ctr = 0; ctr < 1000; ctr++) {
    start = System.nanoTime();
    for (Integer j : a) {
         x = j + 3;
    }
    end = System.nanoTime();
    delta += end - start;
}
System.out.println(Math.pow(10, -6) * delta / 1000 + " milli seconds for [ Integer j : a ] ");


start=0; end = 0; delta = 0;
for (int ctr = 0; ctr < 1000; ctr++) {
    start = System.nanoTime();
    for (int i = 0; i < a.size(); i++) {
         x = a.get(i) + 3;
    }
    end = System.nanoTime();
    delta += end - start;
}
System.out.println(Math.pow(10, -6) * delta / 1000 + " milli seconds for [ int i = 0; i < a.length; i++ ]  ");

int size = a.size();

start=0; end = 0; delta = 0;
for (int ctr = 0; ctr < 1000; ctr++) {
    start = System.currentTimeMillis();

    for (int i = 0; i < size; i++) {
         x = a.get(i) + 3;
    }
    end = System.currentTimeMillis();
    delta += end - start;
}
System.out.println(Math.pow(10, -6) * delta / 1000 + " milli seconds for [ int i = 0; i < size; i++ ] ");

start=0; end = 0; delta = 0;
for (int ctr = 0; ctr < 1000; ctr++) {
    start = System.nanoTime();
    for (int i = size; --i >= 0;) {
         x = a.get(i) + 3;
    }
    end = System.nanoTime();
    delta += end - start;
}
System.out.println(Math.pow(10, -6) * delta / 1000 + " milli seconds for [ int i = size; --i >= 0; ] ");


start=0; end = 0; delta = 0;
for (int ctr = 0; ctr < 1000; ctr++) {
    start = System.nanoTime();
    for (int i = a.size()-1; i >= 0; i--) {
         x = a.get(i) + 3;
    }
    end = System.nanoTime();
    delta += end - start;
}
System.out.println(Math.pow(10, -6) * delta / 1000 + " milli seconds for [ int i = a.size()-1; i >= 0; i-- ] ");

start=0; end = 0; delta = 0;
for (int ctr = 0; ctr < 1000; ctr++) {
    start = System.currentTimeMillis();

    for (int i = 0; i < a.size(); i++) {
         x = a.get(i) + 3;
    }
    end = System.currentTimeMillis();
    delta += end - start;
}
System.out.println(Math.pow(10, -6) * delta / 1000 + " milli seconds for [ int i = 0; i < a.size(); i++ ] ");        

System.out.println(x);
}
3
répondu medv4380 2012-01-27 19:47:09

Ceci est juste une clarification par exemple de la situation.

J'ai testé les exécutions de la "normale" pour la boucle for (int i = 0; i < list.size(); i++) et un micro optimisé pour la boucle for (int i = -1, size = list.size(); ++i < size;). J'ai couru les tests à la fois dans eclipse à partir de la ligne de commande et j'ai remarqué une énorme différence.

Résultats de l'exécution dans eclipse:

Time for Original: 32552 ms   Time for MicroOptimized 32707 ms
Fastest Loop: Original
Slowest loop takes 0.47616121897272057% more time

Résultats de l'exécution de la ligne de commande:

Time for Original: 274489 ms   Time for MicroOptimized 30516 ms
Fastest Loop: MicroOptimized
Slowest loop takes 799.4920697339101% more time

Donc, dans eclipse, les deux boucles for prennent le même temps, mais lorsqu'elles sont exécutées à partir de la ligne de commande, le la version originale prend 800% plus de temps que la version microoptimisée. L'ampleur de la différence souffle mon esprit. Je suppose qu'eclipse utilise une JVM différente qui applique quelques astuces d'optimisation intelligentes.

Cela ne signifie pas que vous devriez commencer à utiliser la version micro optimisée cependant. Dans presque tous les cas, les listes que vous parcourez seront probablement si petites que la différence de performance est négligeable. Et la lisibilité acquise en utilisant la version standard que tout le monde va reconnaître et comprendre beaucoup plus rapidement est plus bénéfique qu'une augmentation de performance non perceptible.

Pour être complet, c'est le code que j'ai couru:

public static void main(String[] args) {
        List<Byte> list = initializeList();
        byte value = 0;
        final int NUM_LOOPS = 100;

        long startOriginal, startOptimized, endOriginal, endOptimized;

        startOptimized = System.currentTimeMillis();
        for (int j = 0; j < NUM_LOOPS; j++) {
            for (int i = -1, size = list.size(); ++i < size;) {
                value = list.get(i);
            }
        }
        endOptimized = System.currentTimeMillis();

        startOriginal = System.currentTimeMillis();
        for (int j = 0; j < NUM_LOOPS; j++) {
            for (int i = 0; i < list.size(); i++) {
                value = list.get(i);
            }
        }
        endOriginal = System.currentTimeMillis();

        System.out.println(value);
        printResults(startOriginal, endOriginal, startOptimized, endOptimized);
    }

    private static void printResults(long startOriginal, long endOriginal,
            long startOptimized, long endOptimized) {

        long timeOriginal = endOriginal - startOriginal;
        long timeOptimized = endOptimized - startOptimized;

        long diff = Math.abs(timeOriginal - timeOptimized);
        long min = Math.min(timeOriginal, timeOptimized);

        System.out.println("Time for Original: " + timeOriginal + " ms"
                + "   Time for MicroOptimized " + timeOptimized + " ms");

        System.out.println("Fastest Loop: "
                + ((timeOriginal < timeOptimized) ? "Original"
                        : "MicroOptimized"));

        System.out.println("Slowest loop takes " + ((double) 100 * diff / min)
                + "% more time");       
    }

    public static List<Byte> initializeList(){
        List<Byte> list = new ArrayList<Byte>();
        final Byte ONE = new Byte((byte) 1);

        for (int i = 0; i < Integer.MAX_VALUE / 10; i++) {
            list.add(ONE);
        }

        return list;
    }
}
2
répondu Alderath 2017-11-16 08:41:28

En outre, si vous vous demandez si l'utilisation d'un appel de méthode en tant que collection source a des implications sur les performances. C'est la méthode appelée de nombreuses fois, la réponse est non. Voici un exemple:

import java.util.*;
public class TestForeach {
    public static void main (String[] args) {

        for (String s : getStrings()) {
            System.out.println("The string was: "+s);
        }
    } 

    private static List<String> getStrings() {
        System.out.println("IN GET STRINGS");
        return Arrays.asList("A","B","C");
    }
}

Cela donnera:

IN GET STRINGS
The string was: A
The string was: B
The string was: C

Par conséquent, la méthode ne sera appelée qu'une seule fois.

1
répondu ACV 2017-06-28 12:03:03

, Soit on va faire. Selon JVM, la seconde peut être quelques cycles d'horloge plus rapides, mais ce sera une différence incommensurable ou insignifiante. Méfiez-vous de ces types de sous-optimisations. Sauf si vous construisez un système en temps réel, où chaque coche CPU compte, ils ajoutent simplement de la complexité et plus de sources d'erreurs.

Je suggère d'utiliser la construction iterator (comme cela a déjà été suggéré)

For (Flower flower: flowers) { ...

C'est clair, flexible et prédicatble.

0
répondu pap 2011-05-23 06:59:47

Mon approche est un peu différente sur ce problème. Pour moi, il vraiment n'importe pas la méthode que vous choisissez. la raison en est la "amélioration des performances" que vous obtiendrez dans la meilleure méthode optimisée sera ~50ms pour 2,500,000 itération!! (selon @David's post). Et évidemment, cette amélioration n'est pas quelque chose comme si vous voudriez perdre votre temps précieux à trouver une solutionoptimisée .

(mais toujours, selon la question originale D'OP, je voudrais également suggérer le la dernière approche.)

Je sais que la réponse est un peu bizarre et peu commune, mais c'est la réalité.

0
répondu Nirmit Shah 2011-05-23 07:42:05

Pour éviter toute cette numérotation et les itérateurs et les vérifications par écrit, le code utilise le code le plus lisible suivant qui a ses performances au maximum. Pourquoi cela a des performances maximales (les détails sont à venir)

for (Object object : aCollection) { 
// Do something here
}

Si l'index est nécessaire, alors: Pour choisir entre les deux formes ci-dessus: La seconde est la meilleure parce que vous utilisez une variable locale pour vérifier. Lorsque la méthode est sortie, la variable est allée à la Corbeille ou de la pile.

0
répondu GingerHead 2012-05-11 08:46:03

Simple mais efficace

 for (ConfigDataModel.VisaTypesBean.AddedBean visatype : visaTypesBeans) {
                            if (visatype.getId() == 24) {

                            }
0
répondu Kaustubh Bhagwat 2018-02-01 09:58:54

Optez pour le premier uniquement car les deux sont identiques, mais en créant une autre variable int redondante dans le second extrait de code.

Alors allez pour le premier extrait de code

for (int i = 0; i < flowers.size(); i++) {
 ...
} 
-1
répondu developer 2011-05-23 06:27:03
String d = JOptionPane.showInputDialog("enter start");
int s = Integer.parseInt(d);
String h = JOptionPane.showInputDialog("enter the end");
int z = Integer.parseInt(h);
for (int a = 1 ; a<10 ; a++) { 
    if (a%2 == 0 ) {
        JOptionPane.showMessageDialog(null, a);
        System.out.print(a);
    }
}    
-1
répondu Mohammed Al Judibe 2015-12-03 17:34:33