On commence à faire du HPC en Java, mais on a toujours le même problème une fois qu'on utilise de la mémoire, et c'est un problème qu'on a déjà abordé quelques pages auparavant, c'est le déterminisme de la libération mémoire. Mais bon, si tu penses qu'on doit faire du Java en HPC, c'est que tu dois avoir raison
Non, je ne fais pas la confusion. Mais je me vois mal faire un traitement HPC en LISP, ou en CaML, ou en autre chose. Java est ce qu'il y a de plus proche des outils actuels (C, C++ ou Fortran). Donc c'est de lui dont je parle pour savoir si on utiliserait des langages à GC pour l'HPC. C#, c'est tout autant limite, même si on a la possibilité d'avoir le déterminisme.
On en revient toujours au même : tout dépend du programme que l'on code à la base.Envoyé par choupitoupa
Moi ce que je peux reprocher au GC c'est que je ne suis dispensé que de faire les delete, pas les Dispose()Envoyé par Matthieu Brucher
A la place d'un "ais-je bien libéré ma mémoire et ma ressource ?" je n'ai plus qu'un "ais-je bien libéré ma ressource ?" ce qui est aussi relou à gérer... Donc je trouve pas l'avancée très intéressante.
Faut voir ce qu'on appelle une appli classique. Si tu entends par là une appli bas de game, qui sert uniquement à un seul utilisateur pour s'amuser seul dans son coin tu as raison.
En revanche, pour une véritable appli professionnelle c'est rarement vrai.
Si tu écris une appli serveur (serveur WEB, service windows...), la question ne se pose même pas : La moindre fuite mémoire deviendra vite une catastrophe.
Si tu écris une appli client/serveur... ben en fait, c'est la même chose. Les appli utilisateurs lourdes sont souvent déployées dans un environnement TSE ou Citrix. Ca facilite considérablement la maintenance, le déploiement et la gestion de la sécurité. Tous les utilisateurs doivent alors se partager la mémoire du serveur.
Et 4 Go lorsque ton appli consomme 200 Mo par utilisateur... ça limite vite le nombre d'utilisateur par serveur.
et ne parlons même pas de la virtualisation la dedans...
Et en fait, dans la plupart des cas, c'est exactement la même chose sans GC : Une appli alloue rarement sa mémoire directement auprès de l'OS pour la rendre chaque fois qu'on fait un Free. Les performances seraient bien trop catastrophiques.
On passe par un gestionnaire de mémoire intermédiaire, directement fournit avec le langage utilisé.
Ce dernier va allouer la mémoire auprès de l'OS par blocs et redécouper ensuite ces blocs en blocs plus petits pour servir les demandes du programme.
Lorsqu'on fait un Free dans le code, on rend la mémoire au gestionnaire de mémoire. Si on en rend suffisament, il rendra à son tour cette mémoire à l'OS.
Dans la pratique, la mémoire est libérée et restituée à l'OS non pas au moment où on libère un bloc dans le code, mais au moment où le gestionnaire de mémoire décide de libérer le bloc principal.
En fait, c'est même pire : Si on a un petit memory leak, ça peut suffir pour que :
- Le bloc principal soit toujours utilisé pour le gestionnaire mémoire et que ce dernier ne le rende jamais à l'OS. Donc un memory leak peut coûter bien plus cher que la taille de l'allocation qui n'a pas été libérée...
- Le bloc principal ne contiennent pas un emplacement continu suffisamment grand pour que l'espace inutilisé soit recyclé afin de servir une allocation qui pourrait tenir sans la fuite mémoire.
On rencontre le même problème, sans fuite mémoire. C'est la fragmentation de la mémoire.
Je code en Delphi. J'ai instrumenté son gestionnaire mémoire pour pouvoir cartographier la mémoire utilisée par mes applis. Le rapport entre la mémoire allouée par l'appli, et la mémoire réellement utilisée est parfois allucinant.
J'ai même constaté une appli qui commence un traitement, se gave en mémoire et s'alloue 1 Go dès le début. Puis au fûr et à mesure du traitement, 90 % des allocations sont libérées, ce qui devrait représenter 900 Mo.
Et bien au fil du traitement, la consomation mémoire auprès de l'OS... reste exactement la même, comme si les libérations n'étaient pas prise en compte.
A la fin du traitement, les 10% restants sont finalement libérés à leur tour. Et là, la conso mémoire de l'appli tombe d'un coup de 1 Go à 20 Mo...
J'ai fait le test avec le gestionnaire de mémoire COM. C'est exactement pareil. Sauf que ses performances se dégradent avec le nombre d'allocations (contraitement à celui de Delphi).
Avec un GC (en tout cas c'est le cas avec celui de .Net), lorsque le GC déclenche, il en profite pour défragmenter la mémoire. On n'a alors plus ce type de problème.
Personnellement, je considère que tout l'intérêt du GC se trouve dans la résolution des problèmes de fragmentation de la mémoire, et dans l'amélioration des performances de l'application du faite que les allocations mémoires ne coutent quasiment plus rien et qu'elles ne se dégrade pas avec le nombre d'allocation (dans un langage "tout objet" on serait très mal autrement).Moi ce que je peux reprocher au GC c'est que je ne suis dispensé que de faire les delete, pas les Dispose()
A la place d'un "ais-je bien libéré ma mémoire et ma ressource ?" je n'ai plus qu'un "ais-je bien libéré ma ressource ?" ce qui est aussi relou à gérer... Donc je trouve pas l'avancée très intéressante.
Pour ce qui est de la "libération automatique de la mémoire", ce n'est rien d'autre qu'un piège à débutant (pardon, on appelle ça un argument marketing...)
La fuite mémoire ce n'est pas exactement la même chose que le gaspillage de ressource. On peut gaspiller sans provoquer aucune fuite. Je ne défend pas vraiment le point de vue de choupitoupa car je ne suis pas d'accord, mais reste qu'une serveur Web qui gaspille des ressources qu'il aurait pû épargner c'est pas forcément la catastrophe. En fait beaucoup d'applications de nos jours gaspillent de la mémoire par exemple. Mais elles ne sont pas pour autant pleine de fuite, c'est-à-dire de ressources qui ne sont jamais libérées.
Bon certes ça dépend toujours du sens qu'on donne à «*fuite mémoire ».
Comment peux-tu dire ça de manière général ? Bien sûr un bon concepteur/développeur aura fait attention à cela. Mais il se trouve que l'un des deux problèmes les plus répandus au niveau du code, c'est la fuite mémoire. D'où l'intérêt d'un GC quand c'est acceptable pour le produit et disponible. Tu dois oublier que ce n'est pas l'appli, mais le développeur qui pense à allouer l'espace.
Je ne fais pas l'apologie du GC. Je ne suis ni pour ni contre.
Pour moi, fuite mémoire, c'est que ce qui est créé à un moment n'est pas détruit au court de l'usage continu du logiciel. Exemple bêbete : je joue à un jeu de guerre, un char est détruit, mais l'instance reste en mémoire(peu importe la cause). Comme on construit un autre char, eh bien la consommation augmente. Pour peu que la bataille s'éternise, la consommation mémoire explose, alors qu'il n'y a toujours que quelques chars qui s'affrontent.
Ca peut être voulu, au cas ou on veuille afficher les épaves. Mais dans le cas contraire, c'est juste une fuite de mémoire. Rien à voir avec une mauvaise allocation de la mémoire(genre on prépare 1000 instances de chars là ou la bataille moyenne n'en aligne que 20).
Les 4 règles d'airain du développement informatique sont, d'après Michael C. Kasten :
1)on ne peut pas établir un chiffrage tant qu'on a pas finalisé la conception
2)on ne peut pas finaliser la conception tant qu'on a pas complètement compris toutes les exigences
3)le temps de comprendre toutes les exigences, le projet est terminé
4)le temps de terminer le projet, les exigences ont changé
Et le serment de non-allégiance :
Je promets de n’exclure aucune idée sur la base de sa source mais de donner toute la considération nécessaire aux idées de toutes les écoles ou lignes de pensées afin de trouver celle qui est la mieux adaptée à une situation donnée.
La différence c'est que pour les ressources ont doit avoir une libération déterministe : on ne peut pas remettre à "plus tard" leur fermeture, car cela engendrerait des problèmes !
C'est ce qui s'approche le plus des fuites mémoires "standard" (où on oublie de libérer la mémoire explicitement) : la mémoire est utilisé par des objets qu'on n'utilise plus...
Matthieu > Il est évident que le coté non-déterministe de la libération de la mémoire via un GC peut s'avérer problématique pour certains types d'application
a++
Tout simplement parce que lorsque tu fais un New, tu n'es pas en contact direct avec les fonctions d'allocation mémoire de l'OS.
Que tu en ais conscience ou non, il y a toujours un gestionnaire mémoire quelque part dans la chaîne du programme (sauf si tu ne travaille qu'avec une pile). Au minimum tu as celui qui t'es fournis par les routines d'allocations mémoire standard de ton langage.
La gestion de la mémoire est un problème très complèxe : Une appli travaille avec deux types de mémoires. La pile et le (ou les) tas.
La pile est très simple à gérer. En revanche pour ce qui concerne le tas ça devient vite très tordu : Tu fais une allocation A, puis une allocation B, puis C. Tu libères B : Tu as un trou dans la mémoire entre A et C. Tu fais une allocation D, il faut voir si tu peux réutiliser le trou entre A et C ou s'il est trop petit, placer le bloc après C....
Les applis modernes effectuent une quantité astronomique d'allocations/libération. C'est encore pire avec les applis objets qui font un très grand nombre d'allocations de très petites taille. Pour gérer tout ça, il faut obligatoirement un gestionnaire mémoire.
Si chaque application effectuait chacune de ses allocations directement auprès de l'OS, ce dernier serait rapidement saturé à devoir gérer des listes de millions de blocs alloués et passerait un temps considérable à rechercher les emplacements libres pour servir une allocation.
D'ailleurs, on le voit très clairement lorsqu'on fait appel au gestionnaire de mémoire COM. Au début, tu fais 100 000 allocations en 20 ms. Lorsque tu commence à atteindre les 5 000 000 d'allocations (et pour remplir 4 Go avec des allocations 16 octets tu peux en faire), il faut 45s pour faire ces mêmes allocations.
Ensuite, tu as encore une autre contrainte : L'OS doit gérer la protection de mémoire pour isoler les processus entre eux et éviter qu'un process ne puisse aller corrompre son voisin.
Cette protection est réalisée de façon matérielle directement par le micro-processeur. Et le matériel ne va pas s'amuser à gérer 4 Go de mémoire (et je ne parle même pas au delà) avec une granularité à l'octet près.
Ca veux dire que l'OS est de toute façon contraint d'allouer des blocs mémoires entier de plusieurs Ko pour chaque demande d'allocation d'un processus. Comme dans ton code, tu fais souvent des allocations beaucoup plus petites, il faut bien un gestionnaire mémoire entre les deux...
Les problèmes que j'ai présenté sont inhérent à la problématique de la gestion de la mémoire. Ils sont résolus par le gestionnaire de mémoire du langage utilisé. Chaque gestionnaire applique sa propre stratégie pour résoudre ces problèmes (et souvent même, ils utilisent plusieurs stratégies), donc selon le gestionnaire ils seront plus ou moins marqués.
Ensuite bien sûr, tu peux développer ton propre gestionnaire mémoire et demander ta mémoire directement à l'OS. Mais je parle d'une appli telle que la plupart des devs vont la coder.
C'est la définition technique d'une fuite mémoire. C'est l'un des trois principal problèmes dans la gestion de la mémoire.Envoyé par el_slapper
Cependant, le memory leak, j'ai envie de dire que c'est le plus bénin. Certes il va faire des ravages lorsqu'il va se produire. Mais quelque part, ce n'est qu'un bug comme un autre.
Le memory leak est très facile à identifier et à corriger.
On dispose de nombreux outils pour y parvenir. Il suffit de monitorer le gestionnaire mémoire. A la fermeture de l'appli, il sera alors capable de nous sortir la liste des allocations qui lui ont été demandées et qui n'ont pas été libérées. Il ne reste alors plus qu'à capturer la pile d'appel au moment de chaque allocation et on connaitra l'origine de la fuite, à la ligne de code près.
Un GC arrive à nous protéger contre ce problème.
Le deuxième problème c'est ce que j'appelle "les libérations tardives". C'est à dire, lorsqu'une application a fini de se servir d'un objet, mais que ce dernier n'est pas détruit pour autant et ne le sera que bien plus tard. C'est très fréquent en Delphi à cause du "owner pattern". Lorsqu'on crée un objet, on lui définit un propriétaire et l'objet sera automatiquement détruit avec le parent. Sauf que si le cycle de vie du parent n'est pas le même que celui de l'enfant, on conserve en mémoire des objets qui n'ont plus lieu d'être. Donc on consomme de la mémoire inutilement.
En principe ce problème a un impacte plus limité, sauf si la durée de vie du parent est grande. Ca peut même dégénérer en memory leak si le parent (ou la chaîne des parents) reste en vie pendant toute la vie de l'application...
Cette situation à pour effet qu'à un instant donné, une application arrive à consommer beaucoup plus de mémoire que ce qu'elle en a réellement besoin. Les pics de consommation dû à un besoin ponctuel risques vite de devenir le rythme de croisière de toute l'appli...
On a le problème avec le cas du parent, mais d'une façon générale, il se présente chaque fois qu'une appli détruit trop tardivement ses objets.
Ainsi, techniquement parlant ce n'est pas une fuite mémoire puisque l'objet est toujours référencé et sera libéré un jour par l'appli, cependant concrètement, le résultat peut vite devenir le même...
Ce problème présente de très gros inconvénients :
- Aucun GC n'est capable de le répérer et de le corriger puisque techniquement ce n'est pas une fuite mémoire.
- Je ne connais aucun outil capable d'identifier ce problème. Et d'ailleurs, dans la pratique, il est même très difficile se savoir si on se trouve dans ce cas de figure.
- Beaucoup de développeur n'ont même pas conscience qu'il s'agit bien là d'un défaut de l'appli.
Enfin, le dernier problème c'est la fragmentation de la mémoire dont j'ai déjà parlé et qui va être d'autant plus aggravée par les deux problèmes précédents.
Sur ce dernier point, un GC peut défragmenter la mémoire et rammener la gestion du tas à celle d'une pile.
En revanche, je ne vois aucune solution avec une gestion non managée du code...
Dans la pratique, tous ces problèmes réunis font rapidement qu'une application peut facilement consommer deux fois plus de mémoire que ce qu'elle en a réellement besoin.
Juste ce qu'il faut pour diviser par deux la monté en charge d'un serveur et doubler les coûts de production...
Je dirais que je ne suis pas 100% d'accord
En effet, un GC ne relève ni de la magie ni de la divination, donc si on garde un lien père-> fils et que le père est vivant, le fils ne sera pas récupéré, même s'il ne sera jamais utilisé.
En revanche, le GC aide quand même grandement. En effet (si je ne m'abuse), l'usage de ce "owner pattern" est principalement dans un but de... gestion de la mémoire ! Donc si on remplace la gestion par destruction explicite (je détruis le père, ça détruit toute sa descendance) par une gestion complètement automatique (un GC), plus besoin de ce pattern, et donc le risque est amplement réduit.
A la base les applications sont conçus statiquement dans Delphi, pour éviter au développeur de faire les allocations mémoires. Au moment de la conception du programme, l'EDI se charge de faire tout le travail d'instanciation des objets à la place du développeur.Envoyé par Franck SORIANO
Cependant, pour un fenêtre windows qui va être l'owner de tous les contrôles visuels qu'elle contient, c'est difficilement concevable de détruire la fenêtre sans ses objets. En revanche, il est possible de créer dynamiquement la fenêtre et la libérer en l'a fermant. J'en ai fait l'expérience et ça peut réduire drastiquement la quantité de mémoire nécessaire à l'application.
Depuis Delphi 2006, le gestionnaire de mémoire est Fastmm de Pierre Le Riche, un projet Open Source. Tes explications sont notamment basées sur le fonctionnement de ce gestionnaire de mémoire.
J'ai relevé sur un post cette remarque:
Si quelqu'un veut l'améliorer, les sources sont disponibles.La principale évolution est l'intégration de la bibilothèque FastMM (http://sourceforge.net/projects/fastmm/) dans le projet. Les (ré)allocations mémoires sont optimisées. Le gain est surtout sensible lors de l'importation des grandes bases de données. Le temps de traitement peut être divisé par un facteur 4 dans certains cas.
Le "owner pattern" est un exemple de cas qui conduit à une libération tardive. Mais il y en à encore beaucoup d'autres. Les plus gros problèmes que j'ai pu rencontrés étaient liés à un gestionnaire de cache où le cache avait été conservé alors qu'on n'en avait plus l'utilité. Il est toujours référencé dans le gestionnaire de caches, donc ce n'est pas un leak. Sauf que je perdais quelques centaines de Mo, avec une tendance à l'aggravation...
Ce qui est encore pire, car on alloue alors automatiquement beaucoup de ressources pour traiter des cas de figure qui seront rencontrés dans peut-être 1% des cas...
C'était la philosophie du RAD dans Delphi. Ca fonctionne pour les petites applis. Mais ce n'est pas utilisable de cette façon à grande échelle.
Pour ce qui concerne la détection des memory leak, oui complètement.Depuis Delphi 2006, le gestionnaire de mémoire est Fastmm de Pierre Le Riche, un projet Open Source. Tes explications sont notamment basées sur le fonctionnement de ce gestionnaire de mémoire.
Pour le reste, j'ai présenté les problèmes qu'on rencontre dans toute gestion de la mémoire.
FastMM fait d'ailleurs un très bon travail, en contournant plus ou moins ces problèmes avec une triple stratégie :
- Pour les allocations de petite taille, il définit des pools d'allocation pré-allouées. De cette façon, les petites allocations peuvent être très rapides et très nombreuses. Chaque pool est dédiié aux allocations d'une certaine taille, ce qui facilite le recyclage des blocs libérés et ainsi limite la fragmentation.
- Pour les allocations de grande taille, si j'ai bien compris, elles sont effectuées directement auprès de l'OS.
- Entre les deux, pour les blocs de taille moyenne j'imagine qu'il doit utiliser une gestion plus traditionnelle de la mémoire avec une liste chaînée des blocs libres.
Le tout, avec un système de promotion qui permet de recycler les blocs mémoires en les faisant passer d'un rôle à un autre.
J'ai l'impression que tu n'as pas compris ma remarque... ton intervention sous entendait qu'il n'y a pas plus de problème en général sans GC. Or, les statistiques établies prouvent le contraire (cf. les travaux de Boehm) : les fuites mémoires sont une des deux erreurs les plus communes.
En fait, c'était vrai en 1998... depuis ça a probablement du diminuer vu que les langages à GC sont de plus en plus présent.
Je ne soutiens pas le GC coute que coute, ce n'est qu'un outil de plus avec ses forces et ses faiblesses. Mais il faut lui reconnaitre son point fort : il évite les fuites mémoires.
Au passage, je partage la définition de el_slapper sur les fuites mémoires. C'est donc plus qu'un gaspillage temporaire mais un gaspillage permanent. Le GC ne permet pas de s'affranchir du gaspillage temporaire et un humain pourrait faire mieux qu'un GC. Mais le GC ne fait pas — enfin s'il n'est pas lui même buggé — de fuite permanente, alors que les humains en font, même les bons ! C'est ça le problème.
Pour des architectures Client/Serveur ou N-tiers, c'est une notion élémentaire à connaître avec ou sans GC puisqu'en .NET tu peux utiliser les instructions Using et Dispose pour forcer la libération de l'objet.Envoyé par Franck SORIANO
En effet. Je voulais surtout souligner le fait qu'on reproche au GC de ne pas libérer immédiatement la mémoire, alors qu'avec une gestion manuelle de la mémoire, ce n'est pas mieux. Et très souvent c'est même pire.
Heu... pour autant que je sache using et dispose ne force pas la libération de l'objet !Envoyé par chaplin
Dispose sert à implementer le dispose-pattern afin de libérer les ressources autre que la mémoire utilisée par un objet.
Using est un mécanisme bien pratique pour s'assurer que le Dispose sera bien appelé dès qu'on sort de la porté de l'objet.
En revanche, dans tous les cas, la mémoire ne sera libérée (et le destructeur ne sera appelé) que lors de la prochaine collecte... après tout dépend de ce qu'on entend par "libération de l'objet".
"Un homme sage ne croit que la moitié de ce qu’il lit. Plus sage encore, il sait laquelle".
Consultant indépendant.
Architecture systèmes complexes. Programmation grosses applications critiques. Ergonomie.
C, Fortran, XWindow/Motif, Java
Je ne réponds pas aux MP techniques
Mais, le dispose pattern de .NET est ressemblant au "Owner pattern" de Delphi, même si les buts sont différents.Implementing a Dispose Method
The pattern for disposing an object, referred to as a dispose pattern, imposes order on the lifetime of an object.
A type's Disposemethod should release all the resources that it owns. It should also release all resources owned by its base types by calling its parent type's Dispose method. The parent type's Dispose method should release all resources that it owns and in turn call its parent type's Dispose method, propagating this pattern through the hierarchy of base types. To help ensure that resources are always cleaned up appropriately, a Dispose method should be callable multiple times without throwing an exception.
There is no performance benefit in implementing the Dispose method on types that use only managed resources (such as arrays) because they are automatically reclaimed by the garbage collector. Use the Dispose method primarily on managed objects that use native resources and on COM objects that are exposed to the .NET Framework. Managed objects that use native resources (such as the FileStream class) implement the IDisposable interface.
Si un gestionnaire de mémoire permet de gagner en performance sur une application, la logique est d'utiliser les recommandations faites dans sa documentation pour arriver aux buts. C'est sûr qu'un moteur mal règlé sera moins efficace, et c'est valable pour n'importe quel outil de développement.
Mais qui s'intéresse à l'ancienne génération ?
Si on regarde le C++ des anciennes générations ce n'est pas glorieux. Mais aujourd'hui on dispose des smart pointers. Comme je l'ai dit je ne suis pas pour ou contre un GC. S'il est disponible je suis content de l'utiliser à peu près tout le temps. Mais je côtoie aussi beaucoup de scientifique qui utilise des HPC et je sais qu'alors il faut parfois contrôler la gestion de mémoire. Ce qui ne fait que prouver qu'il reste beaucoup de recherche à faire ^_^
Vous avez un bloqueur de publicités installé.
Le Club Developpez.com n'affiche que des publicités IT, discrètes et non intrusives.
Afin que nous puissions continuer à vous fournir gratuitement du contenu de qualité, merci de nous soutenir en désactivant votre bloqueur de publicités sur Developpez.com.
Partager