octobre
2009
Je viens de tomber sur un billet de Remi Forax, qui signale l’activation par défaut de l’escape analysis dans Java 7.
Sous ce nom étrange se cache une optimisation de la JVM lui permettant de mieux utiliser la pile (stack) pour stocker les variables locales, ce qui permet normalement de simplifier les allocations/libérations d’objets tout en améliorant les performances…
Je n’ai pas encore pu tester cela, mais le billet de Remi Forax indique des performances 3 fois plus importante lors de la création d’un objet en boucle.
Mais comment utiliser l’allocation sur la pile ?
En général, dans un langage avec une gestion de la mémoire classique
, on peut utiliser deux zones mémoires bien distinctes, selon la manière dont on alloue nos objets :
- Le tas (heap) comporte les éléments alloué de manière dynamique, et qui peuvent donc avoir une durée de vie bien plus grande que la méthode.
- La pile (stack) comporte quand à elle les variables locales de la méthode, qui sont allouées à la déclaration, et libérées à la fin de la méthode.
Elle a pour avantage d’être très simple à utiliser et d’être très performante (allocation/libération d’un seul bloc de la zone mémoire, instructions spécifiques sur certains processeurs, etc.).
Par exemple en C++ cela dépend de la manière dont les objets sont alloués :
Point point(10, 20); // allocation automatique sur la pile
Point* point = new Point(10, 20); // allocation dynamique dans le tas
// (cela nécessite une libération explicite)
Jusqu’à maintenant un des gros reproches de Java venait du fait qu’il utilise principalement le tas : tous les objets y sont en effet alloués et gérés par le Garbage Collector, alors que la pile n’est utilisée que pour stocker les types primitifs et les références des paramètres et variables locales d’une méthode.
Ainsi, même s’il n’est utilisé qu’à l’intérieur d’une méthode, un objet ne peut pas être alloué dans la pile, car le GC ne peut pas la manipuler !
Mais Java 7 et son escape analysis
va changer cela : la JVM a désormais la capacité de détecter le scope des objets à l’exécution, et de déterminer s’ils sont exportés en dehors de la méthode ou non.
- Si un objet est exporté, sa durée de vie pourrait dépasser celle de la méthode et dans ce cas l’utilisation du tas reste obligatoire. On reste alors dans le même cas de figure qu’actuellement.
- Par contre si l’utilisation de l’objet reste limité à la méthode courante, la JVM pourra le stocker dans la pile et donc profiter des améliorations que cela implique, tout en déchargeant la gestion de la mémoire du GC (l’objet sera automatiquement supprimé à la fin de la méthode).
Enfin, la vrai bonne nouvelle dans tout cela vient du fait qu’il s’agit d’une fonctionnalité de la JVM, et que ces améliorations seront automatiquement apportées à tous les programmes Java sans avoir à recompiler ni à faire quelque modification que ce soit… du moment qu’ils s’exécutent sur une JVM 1.7 !
Comment utiliser l’allocation sur la pile ? Il suffit d’utiliser Java 7 pour exécuter vos application…
16 Commentaires + Ajouter un commentaire
Tutoriels
Discussions
- L'apparition du mot-clé const est-il prévu dans une version à venir du JDK?
- Difference de performances Unix/Windows d'un programme?
- Recuperation du nom des parametres
- [REFLEXION] Connaitre toutes les classes qui implémentent une interface
- Classes, méthodes private
- Définition exacte de @Override
- jre 1.5, tomcat 6.0 et multi processeurs
- Possibilité d'accéder au type générique en runtime
- [ fuite ] memoire
Bonjour,
@Mobuis: est ce que vous pouvez nous partager les résultats de tes tests ,comment le test est fait.
Merci d’avance
Je viens de faire des tests et le résultat est assez surprenant.
Je vois bien une différence entre les options activées et désactivées.
Mais je ne vois pas de différence entre les options activées ou sans option.
Comme si les options étaient activé par défaut (test effectué sur une JVM 1.6.0_05)
@cob59 : ok je savais cela (j’en parle même dans le billet), mais j’avais compris que tu parlais d’allocation dynamique sur la pile
a++
« Tiens j’ignorais cela ! Pour moi en C/C++ toutes les allocations dynamiques se retrouvais dans le heap »
Mais justement, en C++ toutes les allocations ne sont *pas* dynamiques.
On peut facilement instancier un objet sur la pile (pas seulement les types primitifs comme en Java).
@professeur shadoko : d’après ce que j’ai vu, les objets finalizable ne pourront pas être alloué sur la pile, car cela « alourdit » la libération (le GC doit le vérifier deux fois avant de réellement le supprimer).
a++
correctif du commentaire précédent: maintenant j’ai lu certains articles et j’ai compris.
question complémentaire (je n’ai pas lu les articles référencés) …
on a effectivement quelque chose de plus rapide mais qu’en est-il alors des opérations liées au glaneur de mémoire (finalize, queues de références, etc…)
Bon je suis pas fana de finalize mais j’ai quelques codes complexes qui le font participer à des opérations de ménage … bien sur localement ça n’a pas un sens faramineux mais le problème c’est que c’est des codes généralistes qui ne savent pas qu’ils sont utilisés localement….
je suppose donc qu’il n’y a pas que la remontée de pile qui efface les objets de la mémoire il doit y avoir des opérations liées à cet effacement.
@adiGuba : Oui c’est courant chez Sun, certaines avancées sur la JVM sont présentes assez tôt dans les JVM, celà dit pour des raisons de validations (car ce genre de mécanisme demande une attention toute particulière lors de la validation) ne sont pas activé par défaut.
D’ailleurs pour cette raison Sun ne fournit pas forcément de support pour ces options dans les JVM ou ces options sont désactivée par défaut.
En tout cas ce genre d’option fait le bonheur de mon Eclipse
@TheNOHDirector : Je viens de faire quelques essai, et apparemment c’était bien prévu pour Java 6 et intégré dans les beta, mais cela a été reporté pour finalement être intégré dans l’update 14 (mais désactivé par défaut).
Les versions précédentes de Java 6 semble accepter ces options mais cela ne semble pas changer grand chose…
J’ai testé le code de Remi avec un Java 6u16 et j’ai un bon facteur 10 en temps de traitement, et surtout aucune charge du GC !
@Uther : j’ai essayé de tester les limites avec une fonction récursive, et je n’obtiens pas de différence d’exécution…
a++
PS : Attention il faut utiliser la JVM server afin de pouvoir utiliser -XX:+DoEscapeAnalysis
A propos je suis en train de ne demander, est ce qu’il y a quelque chose de particulier prévu pour les fonction récursives?
N’y a il pas un risque que des applications qui utilisaient la récursivité sans-soucis, se mettent à faire des stack overflow, à cause l’espace supplémentaire consommé sur la pile.
@cob59 > Tiens j’ignorais cela ! Pour moi en C/C++ toutes les allocations dynamiques se retrouvais dans le heap.
Par curiosité c’est spécifique au C++ ou cela existe aussi en C ?
Cela se traduit comment dans le code ?
a++
J’ai toujours cru que Java était suffisamment malin pour allouer sur la pile quand la portée d’un Objet le permettait… C’est assez consternant. Le C++ a encore de beaux jours devant lui.
On peut déjà faire ça sur les dernières JVM de Java 6, évidement ce n’est pas activé par défaut.
Pour ce qui concerne les optimisations sur la concurrence, essayez de mettre ça :
-XX:+DoEscapeAnalysis
-XX:+UseBiasedLocking
-XX:+EliminateLocks
Il y a un article assez intéressant sur ce sujet dans le blog de Xebia : http://blog.xebia.com/2007/12/21/did-escape-analysis-escape-from-java-6/
Oui cela se fait bien à la compilation… sauf qu’avec Java cela se fait lors de la compilation JIT, donc à l’exécution.
Après une petite recherche je suis tomber là dessus : TS-3412.pdf
Apparemment c’était originellement prévu pour Java 6 mais cela a été reporter pour Java 7, mais d’après le commentaire sur le blog de Remi on pourrait voir arriver cela dans une update de Java 6.
Et en fait les optimisations sont plus nombreuse, car si la JVM détermine que l’objet n’est utilisé que localement, elle peut y apporter de plus grosse optimisation comme y supprimer la synchronisation et optimiser l’accès a ses attributs (inutile de revérifier leurs valeurs si on est sûr qu’il ne peuvent pas être modifié)
Pour l’algorithme cela semble basé sur ce document : milanova-choi.pdf
a++
Ça parait fort intéressant mais je suis curieux de savoir comment cette détection est effectuée. J’aurais naturellement pensé que ce genre de chose se faisait à la compilation.
Ça veux dire que la JVM devra analyser les méthodes avant de les exécuter? Ça ne risque pas de diminuer les performances dans certains cas?
Ça va considérablement accélérer certaines applications qui font du backtracking intensif, comme par exemples les solveurs de Rubik’s Cube.
En fait, pour certains backtracking (par exemple la recherche d’une permutation sur un groupe défini par l’utilisateur), il faudrait même pouvoir allouer un tableau de taille dynamique sur la pile. Et actuellement ça n’est possible qu’en assembleur (donc pas portable sauf si c’est un assembleur pour machine virtuelle).