Est-ce que ++i est vraiment plus rapide que i++ dans les for-loops en java?


En java, je fais généralement une boucle for comme suit:

for (int i = 0; i < max; i++) {
   something
}

Mais récemment, un collègue l'a tapé ainsi:

for (int i = 0; i < max; ++i) {
   something
}

Il a dit que ce dernier serait plus rapide. Est-ce vrai?

Author: IAdapter, 2011-01-28

11 answers

Non, ce n'est pas vrai. Vous pouvez mesurer les performances en chronométrant chaque boucle pour un grand nombre d'itérations, mais je suis à peu près certain qu'elles seront les mêmes.

Le mythe vient de C où ++i était considéré comme plus rapide que i++ car le premier peut être implémenté en incrémentant i puis en le renvoyant. Ce dernier peut être implémenté en copiant la valeur de i dans une variable temporaire, en incrémentant i, puis en renvoyant le temporaire. La première version n'a pas besoin de faire la copie temporaire et tant de gens supposent que c'est plus rapide. Cependant, si l'expression est utilisée comme instruction, les compilateurs C modernes peuvent optimiser la copie temporaire afin qu'il n'y ait pas de différence dans la pratique.

 60
Author: Mark Byers, 2011-01-28 18:41:37

Cette question nécessitait du code d'octet Java. Considérez le code suivant:

public class PostPre {
    public static void main(String args[]) {
        int n = 5;
        loop1(n);
        loop2(n);
    }

    public static void loop1(int n) {
        for (int i = 0; i < n; i++) {}
    }

    public static void loop2(int n) {
        for (int i = 0; i < n; ++i) {}
    }
}

Maintenant, compilez-le et désassemblez-le:

$ javac PostPre.java; javap -c PostPre.class 
Compiled from "PostPre.java"
public class PostPre {
  public PostPre();
    Code:
       0: aload_0       
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return        

  public static void main(java.lang.String[]);
    Code:
       0: iconst_5      
       1: istore_1      
       2: iload_1       
       3: invokestatic  #2                  // Method loop1:(I)V
       6: iload_1       
       7: invokestatic  #3                  // Method loop2:(I)V
      10: return        

  public static void loop1(int);
    Code:
       0: iconst_0      
       1: istore_1      
       2: iload_1       
       3: iload_0       
       4: if_icmpge     13
       7: iinc          1, 1
      10: goto          2
      13: return        

  public static void loop2(int);
    Code:
       0: iconst_0      
       1: istore_1      
       2: iload_1       
       3: iload_0       
       4: if_icmpge     13
       7: iinc          1, 1
      10: goto          2
      13: return        
}

loop1() et loop2() ont le même code octet.

 41
Author: gsingh2011, 2013-09-30 18:08:35

Pour tout optimiseur raisonnablement capable, ils seront exactement les mêmes. Si vous n'êtes pas sûr, regardez le bytecode de sortie ou profilez-le.

 9
Author: Carl Norum, 2011-01-28 18:32:27

Même si c'est le cas, ce dont je doute beaucoup, votre collègue devrait vraiment avoir de meilleures choses à passer son temps à apprendre que comment optimiser une expression de boucle.

 6
Author: biziclop, 2011-01-28 18:31:23

Essayez ceci dans votre environnement

public class IsOptmized {
    public static void main(String[] args) {

        long foo; //make sure the value of i is used inside the loop
        long now = 0; 
        long prefix = 0;
        long postfix = 0;

        for (;;) {
            foo = 0;
            now = System.currentTimeMillis();
            for (int i = 0; i < 1000000000; i++) {
                foo += i;
            }
            postfix = System.currentTimeMillis() - now;

            foo = 0;
            now = System.currentTimeMillis();
            for (int i = 0; i < 1000000000; ++i) {
                foo += i;
            }
            prefix = System.currentTimeMillis() - now;

            System.out.println("i++ " + postfix + " ++i " + prefix + " foo " + foo);
        }
    }
}

Le mien me donne

i++ 1690 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1600 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1611 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1691 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1600 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1691 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1691 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1692 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1691 ++i 1610 foo 499999999500000000

Donc même si ce n'est pas tant que ça, j'estime qu'il y a une différence

 2
Author: Disc-Addict, 2015-02-27 08:37:36

Ce ne sera pas plus rapide. Le compilateur et la JVM avec le JIT feront de la viande hachée de telles différences insignifiantes.

Vous pouvez utiliser les techniques habituelles d'optimisation de boucle pour obtenir des avantages de vitesse, comme le déroulement, le cas échéant.

 1
Author: martona, 2011-01-28 18:32:28

Non il n'y aura pas de différence du tout.

Cela vient de C++ mais même là, il n'y aurait aucune différence dans ce cas. Là où il y a une différence, c'est où i est un objet. i++ devrait faire une copie supplémentaire de l'objet car il doit renvoyer la valeur inchangée d'origine de l'élément tandis que ++je peux renvoyer l'objet modifié afin d'enregistrer une copie.

En c++ avec un objet défini par l'utilisateur, le coût d'une copie peut être important, il est donc utile de le rappeler. Et pour cette raison, les gens ont tendance à l'utiliser pour les variables int aussi, car c'est tout aussi bon de toute façon...

 1
Author: jcoder, 2011-01-31 13:22:34

Décompiler avec "javap-c YourClassName" et voir le résultat et décider à partir de cela. De cette façon, vous voyez ce que le compilateur fait réellement à chaque cas, pas ce que vous pensez qu'il fait. De cette façon, vous voyez aussi POURQUOI une façon est plus rapide que l'autre.

 1
Author: Aulo Aasmaa, 2013-04-05 14:47:42

En Java, il ne devrait y avoir aucune différence - tout compilateur moderne* devrait générer le même code d'octet (juste un iinc) dans les deux cas puisque le résultat de l'expression d'incrément n'est pas utilisé directement.
Il existe une troisième option, toujours le même code octet*:

for (int i = 0; i < max; i += 1) {
   something
}

*testé avec Eclipse du compilateur

 0
Author: user85421, 2011-01-31 13:17:31

En Java, il n'y a pas une telle différence. Java machine interperte le code et peu importe si vous écrivez ++i ou i++, il sera converti en code octet pour exactement le même jeu d'instructions.

Mais en C/C++, il y a une énorme différence et si vous n'utilisez aucun indicateur d'optimisation, votre boucle peut être plus lente jusqu'à 3 fois.

L'utilisation d'indicateurs d'optimisation tels que-O/-O3 forcera le compilateur à rendre le code asembly de sortie plus simple (dans la plupart des cas) et donc plus rapide (dans la plupart des cas).

 0
Author: bartekordek, 2015-08-10 09:11:46

Même celui-ci serait plus rapide, personne ne s'en soucie à l'époque de HotSpot. La première chose que le JIT fait est de supprimer toutes les optimisations effectuées par javac. Après cela, tout est laissé au JIT pour le rendre rapide.

 -2
Author: Ivo Wetzel, 2011-01-28 18:33:18