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




optimize java code (14)

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

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?


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


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.


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


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.


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.


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.


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.


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


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.


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.


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


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




while-loop