IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage Java Discussion :

Gains de performance liés à l'utilisation du mot clé final


Sujet :

Langage Java

  1. #1
    Membre éprouvé

    Homme Profil pro
    Développeur J2EE Senior
    Inscrit en
    Mai 2008
    Messages
    419
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Développeur J2EE Senior
    Secteur : High Tech - Produits et services télécom et Internet

    Informations forums :
    Inscription : Mai 2008
    Messages : 419
    Points : 900
    Points
    900
    Par défaut Gains de performance liés à l'utilisation du mot clé final
    Bonjour à tous

    Dans ma dernière mission, tout le monde avait coché dans les préférences d'Eclipse l'ajout automatique du mot clé "final" partout où c'est possible dans le code. Autre mission autre mœurs, dans l'actuelle les gens ne font pas particulièrement usage du mot clé "final" à part pour déclarer des constantes statiques. J'en ai discuté un petit peu, j'ai creusé sur internet mais c'est flou, la plupart des gens ont l'air de dire que ça peut servir pour la lisibilité mais sans impacter la performance du code.

    Et puis on m'a dit aussi que le compilateur de nos jours est très évolué, qu'il détecte tout seul à la compilation que les variables sont modifiées ou non et qu'il insère toujours le mot clé final lorsqu'il voit que c'est possible.

    J'ai donc réalisé quelques petits tests pour en avoir le cœur net:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package final_effectiveness;
     
    public class AvecFinal {
    	public static String test() {
    		final String a = "1";
    		final int ba = 2;
    		final long bab = 991909000000L;
    		return (a + ba + bab);
    	}
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package final_effectiveness;
     
    public class SansFinal {
    	public static String test() {
    		String a = "1";
    		int ba = 2;
    		long bab = 991909000000L;
    		return (a + ba + bab);
    	}
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package final_effectiveness;
     
    public class Bench {
    	private static final int NBL = 999555;
     
    	public static void main(final String[] args) {
    		long t1, t2;
     
    		t1 = System.currentTimeMillis();
    		for (int i = 0; i < NBL; i++) {
    			AvecFinal.test();
    		}
    		System.out.println("avec final: " + (System.currentTimeMillis() - t1));
     
    		t2 = System.currentTimeMillis();
    		for (int i = 0; i < NBL; i++) {
    			SansFinal.test();
    		}
    		System.out.println("sans final: " + (System.currentTimeMillis() - t2));
     
    	}
     
    }
    Première constatation, la taille des fichiers compilés est différente:
    502 octets pour AvecFinal.class
    760 octets pour SansFinal.class
    Ainsi le fait de rajouter des mots clé "final" dans le source aboutit paradoxalement à un bytecode plus léger.

    Ensuite le bench:
    compliance 1.5, mode debug
    avec final: 16
    sans final: 328

    compliance 1.7 mode prod (NBL passé à 99955566)
    avec final: 47
    sans final: 23369
    Je constate que l'exécution a été 20 fois plus rapide dans le cas où j'ai ajouté les mots clé "final" par rapport à l'autre (500 fois en jdk 1.7 sans les flags de debug)
    Tout ceci me porte à conclure que non le compilateur n'est pas aussi magique qu'on le croit, et que dans le cas général l'usage du mot clé final permet vraiment un (petit) gain de performances.

    Je souhaiterai cependant avoir votre retour sur cette question, est-ce que j'ai raison ou est-ce que je me suis planté quelque part?

  2. #2
    Modérateur

    Profil pro
    Inscrit en
    Septembre 2004
    Messages
    12 559
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2004
    Messages : 12 559
    Points : 21 621
    Points
    21 621
    Par défaut
    Les changements viennent du fait que tu n'utilises que des littéraux pour initialiser tes variables. Dans ce cas-là les variables final deviennent des expressions constantes, et toute expression les utilisant sont aussi des expressions constantes, évaluées par le compilateur et non pas au runtime.
    Forcément cela sera bien plus rapide à l'exécution... Mais dans le monde réel tu ne sauras pas quelle valeur donner aux variables dès l'étape de la compilation, et donc les variables final ne seront pas des expressions constantes, et donc cet avantage n'existera pas.

    Essaie plutôt :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    new String("1");
    Math.min(2, 2);
    Math.max(991909000000L, 1);
    C'est plus des littéraux => c'est plus pareil, hein ?

  3. #3
    Expert éminent sénior
    Avatar de adiGuba
    Homme Profil pro
    Développeur Java/Web
    Inscrit en
    Avril 2002
    Messages
    13 938
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur Java/Web
    Secteur : Transports

    Informations forums :
    Inscription : Avril 2002
    Messages : 13 938
    Points : 23 190
    Points
    23 190
    Billets dans le blog
    1
    Par défaut
    Salut,

    Citation Envoyé par thelvin Voir le message
    Les changements viennent du fait que tu n'utilises que des littéraux pour initialiser tes variables. Dans ce cas-là les variables final deviennent des expressions constantes, et toute expression les utilisant sont aussi des expressions constantes, évaluées par le compilateur et non pas au runtime.
    Gros +1

    Grosso modo avec les final ton code revient à l'équivalent de ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
        public static String test() {
            return "12991909000000";
        }
    On le voit très bien en "lisant" le bytecode avec javap -c

    Avec final les variables locales sont initialisé mais inutilisé, et la méthode retourne directement une chaine avec le bon résultat :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
          0: ldc           #16                 // String 1
          2: astore_0
          3: iconst_2
          4: istore_1
          5: ldc2_w        #18                 // long 991909000000l
          8: lstore_2
          9: ldc           #20                 // String 12991909000000
         11: areturn
    Sans final les variables locales sont utilisé pour recréer une chaine dynamiquement (via un StringBuilder du fait de l'opérateur +) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
     
          0: ldc           #16                 // String 1
          2: astore_0
          3: iconst_2
          4: istore_1
          5: ldc2_w        #18                 // long 991909000000l
          8: lstore_2
          9: new           #29                 // class java/lang/StringBuilder
         12: dup
         13: aload_0
         14: invokestatic  #31                 // Method java/lang/String.valueOf:(
    java/lang/Object;)Ljava/lang/String;
         17: invokespecial #37                 // Method java/lang/StringBuilder."<
    nit>":(Ljava/lang/String;)V
         20: iload_1
         21: invokevirtual #40                 // Method java/lang/StringBuilder.ap
    end:(I)Ljava/lang/StringBuilder;
         24: lload_2
         25: invokevirtual #44                 // Method java/lang/StringBuilder.ap
    end:(J)Ljava/lang/StringBuilder;
         28: invokevirtual #47                 // Method java/lang/StringBuilder.to
    tring:()Ljava/lang/String;
         31: areturn

    Donc dans le cas présent "l'optimisation" consiste à utiliser une constante... ce qui sera rarement le cas dans un cas concret.




    Perso je conseille l'utilisation de final, mais principalement pour des raisons de lecture du code :
    • Pour les constantes bien sûr, que ce soit en static final ou simplement localement dans une méthode.
    • Pour les paramètres de méthodes et variables locales "invariables" (qui ne seront pas modifié après leurs créations).
      Cela permet d'éviter les erreurs de mauvaises affectations ou de "réutilisation trompeuse" pour "optimiser".
    • Pour les variables locales sans valeur initiale, qui prendront une valeur selon différents critères, afin d'être sûr de lui affecter une valeur.
      (le compilateur génère une erreur si on utilise la variable sans l'avoir initialisé)
    • Pour les attributs d'instances, également s'ils n'évolueront pas avec le temps.
      Cela permet d'être sûr de bien les initialisé dans tous les cas (même si cela ne s'adapte pas forcément à tous les cas).




    A noter que le final a un sens différent pour une classe ou méthode, puisqu'il interdit alors respectivement d'hériter de la classe ou de redéfinir la méthode.
    Cela ne doit être utiliser QUE dans ces cas précis.



    a++

  4. #4
    Membre éprouvé

    Homme Profil pro
    Développeur J2EE Senior
    Inscrit en
    Mai 2008
    Messages
    419
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Développeur J2EE Senior
    Secteur : High Tech - Produits et services télécom et Internet

    Informations forums :
    Inscription : Mai 2008
    Messages : 419
    Points : 900
    Points
    900
    Par défaut
    Merci de vos réponses, je redoutais effectivement le biais dans l'expérimentation. J'ai donc modifié mon bench de la manière suivante:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package final_effectiveness;
     
    import java.util.Date;
     
    public class AvecFinal {
    	public static String test(final String s1, final int i1, final long b1, final Date d1) {
    		final String a = "1" + s1;
    		final int ba = 2 + i1;
    		final long bab = 991909000000L + b1;
    		return (a + ba + bab + d1.toString());
    	}
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package final_effectiveness;
     
    import java.util.Date;
     
    public class SansFinal {
    	public static String test(String s1, int i1, long b1, Date d1) {
    		String a = "1" + s1;
    		int ba = 2 + i1;
    		long bab = 991909000000L + b1;
    		return (a + ba + bab + d1.toString());
    	}
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    package final_effectiveness;
     
    import java.util.Date;
     
    public class Bench {
    	private static final int NBL = 9995556;
     
    	public static void main(final String[] args) {
    		long t1, t2;
    		long b1 = System.currentTimeMillis();
    		String s1 = "Hello" + b1;
    		int i1 = s1.length();
    		Date d1 = new Date();
     
    		t1 = System.currentTimeMillis();
    		for (int i = 0; i < NBL; i++) {
    			AvecFinal.test(s1, i1, b1, d1);
    		}
    		System.out.println("avec final: " + (System.currentTimeMillis() - t1));
     
    		t2 = System.currentTimeMillis();
    		for (int i = 0; i < NBL; i++) {
    			SansFinal.test(s1, i1, b1, d1);
    		}
    		System.out.println("sans final: " + (System.currentTimeMillis() - t2));
     
    	}
     
    }
    Première constatation, dans le cas des classes qui ont été compilés pour la JVM 1.7 sans les inserts de débogage, les deux fichiers .class font rigoureusement la même taille (680).
    De même pour la jvm 1.5 avec flags de debug (1013).
    Ensuite le bench:
    compliance 1.5, mode debug
    avec final: 1503
    sans final: 1457

    compliance 1.7 mode prod (NBL passé à 9995556)
    avec final: 10852
    sans final: 10869
    On observe qu'effectivement les résultats deviennent sensiblement identiques. Comme il était probable que le toString sur la date était le plus gros consommateur, j'ai fait un autre test témoin qui m'a donné les mêmes ordres de grandeurs. Merci pour ces éclaircissements.

  5. #5
    Expert éminent sénior
    Avatar de adiGuba
    Homme Profil pro
    Développeur Java/Web
    Inscrit en
    Avril 2002
    Messages
    13 938
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur Java/Web
    Secteur : Transports

    Informations forums :
    Inscription : Avril 2002
    Messages : 13 938
    Points : 23 190
    Points
    23 190
    Billets dans le blog
    1
    Par défaut
    Le mot-clef final sur une variable ne concerne que la compilation.
    Le compilateur va soit en faire une constante (si la valeur peut être déterminé à la compilation), soit simplement vérifier que la variable ne sera initialisé qu'une seule fois.

    Mais cela n'a aucun impact sur le bytecode généré, et il ne peut donc pas y avoir de différence à l'exécution (tant qu'on n'est pas dans le cas de la constante bien sûr).


    Malgré tout je recommande l'utilisation de final sur les variables, lorsque c'est possible, pour un code plus propre.



    a++

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Utilisation abusive mot clé final
    Par jnmlme dans le forum Langage
    Réponses: 9
    Dernier message: 10/10/2006, 08h53
  2. Question sur l'utilisation du mot réservé static
    Par flash2590 dans le forum Langage
    Réponses: 4
    Dernier message: 10/04/2006, 00h20
  3. [Clé unique/Clé composite] Gain de performances ?
    Par Nounoursonne dans le forum Oracle
    Réponses: 2
    Dernier message: 30/01/2006, 08h59
  4. [Packages]Un véritable gain de performance ?
    Par New dans le forum Oracle
    Réponses: 7
    Dernier message: 28/10/2005, 14h19
  5. [Débutant] Utilisation du mot clé ASSERT
    Par analysiz dans le forum Eclipse Java
    Réponses: 2
    Dernier message: 29/07/2004, 11h43

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo