java performance - Déclarer des variables à l'intérieur ou à l'extérieur d'une boucle




10 Answers

La portée des variables locales doit toujours être la plus petite possible.

Dans votre exemple, je suppose que str n'est pas utilisé en dehors de la boucle while, sinon vous ne poseriez pas la question, car la déclarer dans la boucle while ne serait pas une option, car elle ne serait pas compilée.

Donc, puisque str n'est pas utilisé en dehors de la boucle, la plus petite portée possible pour str est dans la boucle while.

Donc, la réponse est catégoriquement que str doit absolument être déclaré dans la boucle while. Pas de si, pas de ands, pas de mais.

Le seul cas où cette règle pourrait être violée est si pour une raison quelconque, il est vital que chaque cycle d'horloge doit être évincé du code, auquel cas vous pourriez envisager d'instancier quelque chose dans une portée externe et de le réutiliser au lieu de ré-instanciation à chaque itération d'une portée interne. Cependant, cela ne s'applique pas à votre exemple, en raison de l'immutabilité des chaînes dans Java: une nouvelle instance de str sera toujours créée au début de votre boucle et elle devra être jetée à la fin de votre boucle, donc il il n'y a aucune possibilité d'optimiser là.

EDIT: (en injectant mon commentaire ci-dessous dans la réponse)

Dans tous les cas, la bonne façon de faire est de bien écrire tout votre code, d'établir une exigence de performance pour votre produit, de mesurer votre produit final par rapport à cette exigence, et si cela ne le satisfait pas, d'optimiser les choses. Et ce qui finit généralement par arriver, c'est que vous trouvez des moyens de fournir des optimisations algorithmiques formelles et sympas dans quelques endroits qui rendent notre programme conforme à ses exigences de performance au lieu d'avoir à parcourir toute votre base de code. afin de serrer les cycles d'horloge ici et là.

tuning optimize

Pourquoi ce qui suit fonctionne-t-il bien?

String str;
while (condition) {
    str = calculateStr();
    .....
}

Mais celui-ci est dit dangereux / incorrect:

while (condition) {
    String str = calculateStr();
    .....
}

Est-il nécessaire de déclarer des variables en dehors de la boucle?




La déclaration d'objets dans la plus petite portée améliore la lisibilité .

La performance n'a pas d'importance pour les compilateurs d'aujourd'hui (dans ce scénario)
Du point de vue de la maintenance, la 2ème option est meilleure.
Déclarez et initialisez les variables au même endroit, dans la portée la plus étroite possible.

Comme Donald Ervin Knuth l'a dit:

"Nous devrions oublier les petites efficacités, disons environ 97% du temps: l'optimisation prématurée est la racine de tous les maux"

c'est-à-dire une situation où un programmeur laisse les considérations de performance affecter la conception d'un morceau de code. Cela peut entraîner une conception qui n'est pas aussi propre qu'elle aurait pu l'être ou un code incorrect, car le code est compliqué par l' optimisation et le programmeur est distrait par l' optimisation .




A l'intérieur, moins la variable est visible dans le meilleur.




Veuillez passer à la réponse mise à jour ...

Pour ceux qui se soucient des performances, supprimez System.out et limitez la boucle à 1 octet. En utilisant double (test 1/2) et en utilisant String (3/4) les temps écoulés en millisecondes sont donnés ci-dessous avec Windows 7 Professional 64 bits et JDK-1.7.0_21. Les bytecodes (également donnés ci-dessous pour test1 et test2) ne sont pas identiques. J'étais trop fainéant pour tester avec des objets mutables et relativement complexes.

double

Test1 pris: 2710 msecs

Test2 pris: 2790 msecs

Chaîne (remplacez simplement le double par une chaîne dans les tests)

Test3 a pris: 1200 msecs

Test4 a pris: 3000 msecs

Compiler et obtenir le bytecode

javac.exe LocalTest1.java

javap.exe -c LocalTest1 > LocalTest1.bc


public class LocalTest1 {

    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();
        double test;
        for (double i = 0; i < 1000000000; i++) {
            test = i;
        }
        long finish = System.currentTimeMillis();
        System.out.println("Test1 Took: " + (finish - start) + " msecs");
    }

}

public class LocalTest2 {

    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();
        for (double i = 0; i < 1000000000; i++) {
            double test = i;
        }
        long finish = System.currentTimeMillis();
        System.out.println("Test1 Took: " + (finish - start) + " msecs");
    }
}


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

  public static void main(java.lang.String[]) throws java.lang.Exception;
    Code:
       0: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
       3: lstore_1
       4: dconst_0
       5: dstore        5
       7: dload         5
       9: ldc2_w        #3                  // double 1.0E9d
      12: dcmpg
      13: ifge          28
      16: dload         5
      18: dstore_3
      19: dload         5
      21: dconst_1
      22: dadd
      23: dstore        5
      25: goto          7
      28: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
      31: lstore        5
      33: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      36: new           #6                  // class java/lang/StringBuilder
      39: dup
      40: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
      43: ldc           #8                  // String Test1 Took:
      45: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      48: lload         5
      50: lload_1
      51: lsub
      52: invokevirtual #10                 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
      55: ldc           #11                 // String  msecs
      57: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      60: invokevirtual #12                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      63: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      66: return
}


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

  public static void main(java.lang.String[]) throws java.lang.Exception;
    Code:
       0: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
       3: lstore_1
       4: dconst_0
       5: dstore_3
       6: dload_3
       7: ldc2_w        #3                  // double 1.0E9d
      10: dcmpg
      11: ifge          24
      14: dload_3
      15: dstore        5
      17: dload_3
      18: dconst_1
      19: dadd
      20: dstore_3
      21: goto          6
      24: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
      27: lstore_3
      28: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      31: new           #6                  // class java/lang/StringBuilder
      34: dup
      35: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
      38: ldc           #8                  // String Test1 Took:
      40: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      43: lload_3
      44: lload_1
      45: lsub
      46: invokevirtual #10                 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
      49: ldc           #11                 // String  msecs
      51: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      54: invokevirtual #12                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      57: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      60: return
}

RÉPONSE MISE À JOUR

Il n'est vraiment pas facile de comparer les performances avec toutes les optimisations JVM. Cependant, c'est un peu possible. Meilleur test et résultats détaillés dans Google Caliper

  1. Quelques détails sur le blog: Devriez-vous déclarer une variable dans une boucle ou avant la boucle?
  2. Référentiel GitHub: https://github.com/gunduru/jvdt
  3. Résultats de test pour la boucle double et 100M (et oui tous les détails de la JVM): https://microbenchmarks.appspot.com/runs/b1cef8d1-0e2c-4120-be61-a99faff625b4

  • DéclaréAvant 1 759.209 ns
  • DéclaréInside 2 242.308 ns

Code d'essai partiel pour double déclaration

Ce n'est pas identique au code ci-dessus. Si vous ne faites que coder une boucle fictive, la machine virtuelle Java l'ignore et vous devez au moins attribuer et renvoyer quelque chose. Ceci est également recommandé dans la documentation de Caliper.

@Param int size; // Set automatically by framework, provided in the Main
/**
* Variable is declared inside the loop.
*
* @param reps
* @return
*/
public double timeDeclaredInside(int reps) {
    /* Dummy variable needed to workaround smart JVM */
    double dummy = 0;

    /* Test loop */
    for (double i = 0; i <= size; i++) {

        /* Declaration and assignment */
        double test = i;

        /* Dummy assignment to fake JVM */
        if(i == size) {
            dummy = test;
        }
    }
    return dummy;
}

/**
* Variable is declared before the loop.
*
* @param reps
* @return
*/
public double timeDeclaredBefore(int reps) {

    /* Dummy variable needed to workaround smart JVM */
    double dummy = 0;

    /* Actual test variable */
    double test = 0;

    /* Test loop */
    for (double i = 0; i <= size; i++) {

        /* Assignment */
        test = i;

        /* Not actually needed here, but we need consistent performance results */
        if(i == size) {
            dummy = test;
        }
    }
    return dummy;
}



Je pense que la meilleure ressource pour répondre à votre question serait le post suivant:

Différence entre déclarer des variables avant ou en boucle?

Selon ma compréhension, cette chose dépendrait de la langue. IIRC Java optimise cela, donc il n'y a pas de différence, mais JavaScript (par exemple) fera l'allocation de mémoire entière à chaque fois dans la boucle. En Java en particulier, je pense que la seconde s'exécuterait plus vite.




Les variables doivent être déclarées aussi proches de l'endroit où elles sont utilisées que possible.

Cela rend le RAII (Initialisation de l'Acquisition de Ressources) plus facile.

Cela permet de limiter la portée de la variable. Cela permet à l'optimiseur de mieux fonctionner.




Comme beaucoup de personnes l'ont souligné,

String str;
while(condition){
    str = calculateStr();
    .....
}

n'est pas meilleur que ça:

while(condition){
    String str = calculateStr();
    .....
}

Donc, ne déclarez pas les variables en dehors de leur portée si vous ne les réutilisez pas ...




Vraiment, la question posée ci-dessus est un problème de programmation. Comment aimeriez-vous programmer votre code? Où avez-vous besoin d'accéder au 'STR'? Il n'est pas utile de déclarer une variable utilisée localement en tant que variable globale. Les bases de la programmation, je crois.




Ces deux exemples aboutissent à la même chose. Cependant, le premier vous fournit l'utilisation de la variable str dehors de la boucle while; la seconde ne l'est pas.




Attention pour presque tout le monde dans cette question: Voici un exemple de code où à l'intérieur de la boucle il peut facilement être 200 fois plus lent sur mon ordinateur avec Java 7 (et la consommation de mémoire est également légèrement différente). Mais il s'agit d'allocation et pas seulement de portée.

public class Test
{
    private final static int STUFF_SIZE = 512;
    private final static long LOOP = 10000000l;

    private static class Foo
    {
        private long[] bigStuff = new long[STUFF_SIZE];

        public Foo(long value)
        {
            setValue(value);
        }

        public void setValue(long value)
        {
            // Putting value in a random place.
            bigStuff[(int) (value % STUFF_SIZE)] = value;
        }

        public long getValue()
        {
            // Retrieving whatever value.
            return bigStuff[STUFF_SIZE / 2];
        }
    }

    public static long test1()
    {
        long total = 0;

        for (long i = 0; i < LOOP; i++)
        {
            Foo foo = new Foo(i);
            total += foo.getValue();
        }

        return total;
    }

    public static long test2()
    {
        long total = 0;

        Foo foo = new Foo(0);
        for (long i = 0; i < LOOP; i++)
        {
            foo.setValue(i);
            total += foo.getValue();
        }

        return total;
    }

    public static void main(String[] args)
    {
        long start;

        start = System.currentTimeMillis();
        test1();
        System.out.println(System.currentTimeMillis() - start);

        start = System.currentTimeMillis();
        test2();
        System.out.println(System.currentTimeMillis() - start);
    }
}

Conclusion: En fonction de la taille de la variable locale, la différence peut être énorme, même avec des variables moins grandes.

Juste pour dire que parfois, à l'extérieur ou à l'intérieur de la boucle importe.




Related

java optimization design while-loop