Mieux programmer en Java Peter Haggar Éditions Eyrolles ISBN : 2-212-09171-0 2000
PARTIE 1 l exécution de l instruction return, cependant, le contrôle est transféré au bloc finally en // 2. Cela provoque l exécution de l instruction return 4, qui va entraîner dans la method2 le renvoi de l entier 4. Habituellement, les programmeurs pensent que lorsqu ils exécutent une instruction return, ils quittent immédiatement la méthode dans laquelle ils sont. Dans Java, cela n est plus vrai en ce qui concerne l emploi de finally. (On peut aussi entrer dans un bloc finally par une instruction break ou continue qui s exécute à l intérieur d un bloc try.) Cette caractéristique particulière de finally peut provoquer une confusion et s avère propice à de longues sessions de débogage. Pour éviter ce piège, assurez-vous que vous ne sortez pas d une instruction return, break ou continue depuis l intérieur d un bloc try. Si vous ne pouvez éviter cette éventualité, vérifiez que l existence d un finally ne modifie pas la valeur de retour de votre méthode. Ce problème particulier peut survenir au cours de la maintenance de votre code, même avec une conception et une implémentation prudentes. De bons commentaires et un code passé soigneusement en revue permettent d y parer. Placer les blocs try/catch en dehors des boucles Les exceptions peuvent avoir un impact néfaste sur les performances de votre code. Pour savoir si les exceptions affectent négativement les performances, vous devez vérifier comment vous avez structuré votre code et si votre JVM se sert d un compilateur JIT qui optimise votre code lors de son exécution. Deux éléments sont à considérer : L effet produit par le traitement d une exception. Les effets des blocs try/catch sur votre code. Vous ne pouvez pas traiter une exception comme bon vous semble car, après tout, que sont les exceptions Java? Ce sont des objets et, comme tout objet, ils ont besoin d être créés. Or la création d objets est coûteuse en termes de ressources (voir l ATELIER 32). Ainsi, traiter une exception implique certaines dépenses de ressources. Pour récupérer une exception, vous allez écrire quelque chose de similaire à ceci : throw new MyException; Ce code crée un nouvel objet et en transfère ensuite le contrôle à un bloc catch ou finally ou à la méthode appelante. Comme le traitement des exceptions entraîne certains coûts, ne les employez que pour des conditions d erreur. Lorsque les exceptions sont utilisées pour les contrôles de flux, le code n est pas aussi efficace ou clair que si vous aviez employé des constructeurs de flux classiques (voir l ATELIER 24). Limitez l usage des exceptions aux seules conditions d erreur et d échec. Vous voulez que votre code s exécute rapidement quand tout se passe bien et vous ne vous souciez généralement pas du temps que cela prendra pour qu une erreur survienne. Lorsque vous vous inquiétez de l aspect performance de Java, vous devez tenir compte de la JVM et du système d exploitation utilisés. Des différences de temps d exécution sont observées sur des JVM qui exécutent du code identique. Par conséquent, vous devez mettre en place des niveaux d observation (profiling) sur vos systèmes pour déterminer les différences
2 Mieux programmer en Java de performance. Les informations décrites dans cet ATELIER ont été obtenues avec la configuration matérielle et logicielle dont le détail est donné dans l introduction de la partie 4 (juste avant l ATELIER 28). Placer des blocs try/catch à l intérieur de boucles peut ralentir l exécution du code comme le montre l extrait de programme suivant : class ExcPerf public void method1(int size) int[] ia = new int[size]; try for (int i = 0; i<size; i++) ia[i] = i; catch (Exception e) // Exception volontairement ignorée public void method2(int size) int[] ia = new int[size]; for (int i=0; i<size; i++) try ia[i] = i; catch (Exception e) // Exception volontairement ignorée Les méthodes method1 et method2 sont presque identiques. Le code de method1 possède un bloc try/catch en dehors de sa boucle for, alors que le code de method2 a lui son bloc try/catch à l intérieur de sa boucle for. Notez qu aucune des deux méthodes ne traite d exception. Elles contiennent seulement un bloc try/catch entourant du code. L exécution de ce code avec le compilateur JIT donne des résultats identiques pour les deux méthodes en termes de rapidité d exécution. Cependant, si vous lancez ce code avec le compilateur JIT désactivé ou avec une JVM ne comprenant pas de JIT, method2 est approximativement 21 % plus lente que method1. Cela peut vous conduire à penser que les bytecodes générés pour les deux méthodes diffèrent considérablement. En fait, ils sont quasiment identiques, excepté le fait que le bytecode de method2 contient quelques codes opérations supplémentaires pour le bloc try/catch inclus dans la boucle. Lorsqu il est exécuté avec un compilateur JIT, ce code est optimisé afin de n avoir aucun effet néfaste sur le temps d exécution. Sans un JIT, cependant, chaque itération de la boucle provoque une bifurcation supplémentaire. Ce qui suit représente le bytecode généré pour les deux méthodes :
PARTIE 3 Method void method1(int) 0 iload_1 // Place la valeur stockée à l index 1 de la table // des variables locales (taille) sur la pile. 1 newarray int // Prend le paramètre size (taille) et crée un // nouveau tableau d entiers avec des éléments // size. Place la référence tableau nouvellement // créée (ia). 3 astore_2 // Prend la référence tableau (ia) et la // stocke à l index 2 de la table des variables // locales. 4 iconst_0 // Commencement du bloc try. Place 0 pour la // valeur initiale du compteur de boucle (i). 5 istore_3 // Prend 0(i) et le stocke à l index 3 de // la table des variables locales. 6 goto 16 // Saute à l emplacement 16. 9 aload_2 // Place la référence objet (ia) à l index 2 de la // table des variables locales. 10 iload_3 // Place la valeur à l index 3(i). 11 iload_3 // Place la valeur à l index 3(i). 12 iastore // Prend les trois premières valeurs du // haut. // Stocke la valeur de i en index i dans le // tableau(ia). 13 iinc 3 1 // Incrémente le compteur de loop (i) stocké à // l index 3 de la table des variables locales. 16 iload_3 // Place la valeur à l index 3(i). 17 iload_1 // Place la valeur à l index 1(size). 18 if_icmplt 9 // Place à la fois le compteur de boucle (i) et // size. Saute à l emplacement 9 si i est plus // petit que size. 21 goto 25 // Fin du bloc try. Saute à l emplacement 25. 24 pop // Début d un bloc catch. 25 return // Retour de la méthode. Exception table : // Si une java.lang.exception survient from to target type // entre l emplacement 4 (inclusif) et // l emplacement 21 (exclusif). Saute à 4 21 24 <Class.java.lang.Exception> // la position 24. Method void method2(int) 0 iload_1 // Place la valeur stockée à l index 1 de la table // des variables locales (size) sur la pile. 1 newarray int // Prend le paramètre size et crée // un nouveau tableau d entiers avec des éléments // size. Place la référence tableau nouvellement // créée (ia). 3 astore_2 // Prend la référence tableau (ia) et la // stocke à l index 2 de la table des // variables locales. 4 iconst_0 // Place 0 pour la valeur // initiale du compteur de boucle (i).
4 Mieux programmer en Java 5 istore_3 // Prend 0(i) et le stocke en index 3 de // la table des variables locales. 6 goto 23 // Saute à l emplacement 23. 9 aload_2 // Début du bloc try. Place la référence // objet (ia) en index 2. 10 iload_3 // Place la valeur à l index 3(i). 11 iload_3 // Place la valeur à l index 3(i). 12 iastore // Prend les trois premières valeurs du // haut. Stocke la valeur de i à l index i dans // le tableau(ia). 13 goto 20 // Fin du bloc try. Saute à l emplacement 20. 16 pop // Début de bloc catch. 17 goto 20 // Saute à l emplacement 20. 20 iinc 3 1 // Incrémente de 1 le compteur de boucle i stocké // à l index 3 de la table des variables locales. 23 iload_3 // Place la valeur à l index 3(i). 24 iload_1 // Place la valeur à l index 1(size). 25 if_icmplt 9 // Fait sauter à la fois le compteur de // boucle (i) et size. // Saute à l emplacement 9 si i est plus petit // que size... 28 return // Retour de la méthode. Exception table : // Si une java.lang.exception survient from to target type // entre l emplacement 9 (inclusif) et 9 13 16 <Class.java.lang.Exception> // l emplacement 13 (exclusif) saute à // la position 16. Les flèches indiquent les constructions de la boucle pour les deux méthodes. Au vu des différences engendrées, une bonne maîtrise de la programmation conduit à placer les blocs try/ catch en dehors des boucles. Vos clients peuvent utiliser une JVM sans JIT ou, en raison d économie de mémoire, désactiver un JIT. Par conséquent, vous ne pouvez supputer que les blocs try/catch situés à l intérieur des boucles n affecteront pas négativement les performances du code. Pour exécuter un fichier appelé Test.class avec le JIT désactivé, faites ce qui suit : java -Djava.compiler=NONE test Cette commande désactive le JIT pour la JVM en cours d utilisation pendant que le programme Test s exécute.