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




tuning optimize (17)

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?


Answers

si vous voulez utiliser str dehors de looop aussi; déclarez-le dehors. Sinon, la 2ème version est correcte.


Si vous n'avez pas besoin d'utiliser la str après la boucle while (portée liée), la seconde condition

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

est meilleur puisque si vous définissez un objet sur la pile seulement si la condition est vraie. C'est à dire l'utiliser si vous en avez besoin


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


J'ai comparé le code octet de ces deux exemples (similaires):

Regardons 1. exemple :

package inside;

public class Test {
    public static void main(String[] args) {
        while(true){
            String str = String.valueOf(System.currentTimeMillis());
            System.out.println(str);
        }
    }
}

après javac Test.java , javap -c Test vous obtiendrez:

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

public static void main(java.lang.String[]);
  Code:
   0:   invokestatic    #2; //Method java/lang/System.currentTimeMillis:()J
   3:   invokestatic    #3; //Method java/lang/String.valueOf:(J)Ljava/lang/String;
   6:   astore_1
   7:   getstatic       #4; //Field java/lang/System.out:Ljava/io/PrintStream;
   10:  aload_1
   11:  invokevirtual   #5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   14:  goto    0

}

Regardons 2. l'exemple :

package outside;

public class Test {
    public static void main(String[] args) {
        String str;
        while(true){
            str =  String.valueOf(System.currentTimeMillis());
            System.out.println(str);
        }
    }
}

après javac Test.java , javap -c Test vous obtiendrez:

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

public static void main(java.lang.String[]);
  Code:
   0:   invokestatic    #2; //Method java/lang/System.currentTimeMillis:()J
   3:   invokestatic    #3; //Method java/lang/String.valueOf:(J)Ljava/lang/String;
   6:   astore_1
   7:   getstatic       #4; //Field java/lang/System.out:Ljava/io/PrintStream;
   10:  aload_1
   11:  invokevirtual   #5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   14:  goto    0

}

Les observations montrent qu'il n'y a pas de différence entre ces deux exemples. C'est le résultat des spécifications de la JVM ...

Mais au nom de la meilleure pratique de codage, il est recommandé de déclarer la variable dans la plus petite portée possible (dans cet exemple, elle est à l'intérieur de la boucle, car c'est le seul endroit où la variable est utilisée).


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;
}

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 .


La déclaration de chaîne str en dehors de la boucle wile lui permet d'être référencée à l'intérieur et à l'extérieur de la boucle while. La déclaration de chaîne str à l'intérieur de la boucle while permet de la référencer uniquement dans la boucle while.


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.


Selon le guide Google Android Development, la portée de la variable doit être limitée. Veuillez vérifier ce lien:

Limite variable portée


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.


Vous avez un risque de NullPointerException si votre méthode calculateStr() renvoie null , puis vous essayez d'appeler une méthode sur str.

Plus généralement, évitez d'avoir des variables avec une valeur nulle . C'est plus fort pour les attributs de classe, d'ailleurs.


Une solution à ce problème pourrait être de fournir une variable variable encapsulant la boucle while:

{
  // all tmp loop variables here ....
  // ....
  String str;
  while(condition){
      str = calculateStr();
      .....
  }
}

Ils seraient automatiquement dé-référence lorsque la portée externe se termine.


Je pense que la taille de l'objet compte aussi. Dans l'un de mes projets, nous avions déclaré et initialisé un grand tableau bidimensionnel qui faisait que l'application jetait une exception de mémoire insuffisante. Nous avons plutôt déplacé la déclaration hors de la boucle et effacé le tableau au début de chaque itération.


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.


La variable str sera disponible et réservera de l'espace en mémoire même après avoir été exécuté en dessous du code.

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

La variable str ne sera pas disponible et la mémoire sera libérée qui a été allouée pour la variable str dans le code ci-dessous.

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

Si nous suivions le second, cela réduirait sûrement la mémoire de notre système et augmenterait les performances.


do {
  stuff()
} while (condition())

->

while True:
  stuff()
  if not condition():
    break

Vous pouvez faire une fonction:

def do_while(stuff, condition):
  while condition(stuff()):
    pass

Mais 1) C'est moche. 2) La condition devrait être une fonction avec un paramètre, supposé être rempli par des trucs (c'est la seule raison de ne pas utiliser la boucle while classique).





java optimization design while-loop