La tambouille interne du Garbage Collector... mystères !

La tambouille interne du Garbage Collector... mystères ! - C#/.NET managed - Programmation

Marsh Posté le 08-12-2004 à 17:41:59    

Salut tlm,
 
Je suis en train de finaliser une appli client-serveur, et je m'occupe d'optimiser les quantités de mémoires utilisées à droite et à gauche, en essayant de virer ce qui n'est pas utile.
Je viens de m'appercevoir d'une chose assez surprenante. Le serveur est un service windows, et à ce titre, ne créé pas de fenêtres. Cependant, pour pouvoir débugguer tranquillement, on peut le lancer en temps qu'application standart : il créé alors une fenetre de debug, avec un log, entre autre. A ce moment là, le serveur occupe environ 27Mo en ram. Si je réduit la fenetre, il n'occupe plus que 2Mo !!! Bon, bah, 25 Mo pour ma pauvre fenêtre, je trouvais ca un peu beaucoup, mais bon, passons, vu qu'elle ne servira pas dans le produit final.
Lorsque le serveur est lancé en temps que service, il occupe 20Mo, ce qui est un peu beaucoup à mon gout. Sachant que le Garbage Collector arrive à le réduire à 2Mo quand il s'agit d'une application, pourquoi est ce que mon service en occupe autant ?
 
J'ai essayé de placer des GC.Collect() à certains endroits, mais je ne vois strictement aucune différence, comme si cette instruction ne faisait rien.
 
Je me demande donc comment le garbage collector fonctionne ?
Va-t-il libérer de la mémoire si windows en a besoin, vu que visiblement, il y a 25 Mo occupés qui ne servent pas vraiment, ç première vue ?
Y-a-t'il un moyen pour lui faire lacher la mémoire non utilisée ?
 
Bref, comment fonctionne ce sacré GC ?

Reply

Marsh Posté le 08-12-2004 à 17:41:59   

Reply

Marsh Posté le 09-12-2004 à 00:59:29    

up

Reply

Marsh Posté le 09-12-2004 à 01:06:39    

j'ai rien compris, si tu lances ton appli en mode debug depuis ton ide, biensur que ça va bouffer plus.
 
Ton problème, c'est que tu as une différence d'occupation mémoire entre ton appli lancée sans debug dans ton IDE et quand tu lances l'appli tout seule ?

Reply

Marsh Posté le 09-12-2004 à 10:28:50    

Citation :

Bref, comment fonctionne ce sacré GC ?


Il y a un tres bon article dans le bouquin de patrick smacchia "Pratique de .NET et C#"
Le fonctionnement du GC est expliqué , mais gare aux maux de tete.

Reply

Marsh Posté le 09-12-2004 à 11:04:53    

Un GC alloue un bon gros paquet de mémoire dès le début. A cela s'ajoute le JIT qui alloue de la mémoire pour compiler ton code au runtime, + les classes que tu utilises.  
C'est plutot tes 2Mo qui sont louches.


---------------
FAQ fclc++ - FAQ C++ - C++ FAQ Lite
Reply

Marsh Posté le 09-12-2004 à 15:38:55    

c'est quoi qui fait 2 Mo, les pages allouées, ou le Working Set ? (pages utilisées à instant T en ram physique)

Reply

Marsh Posté le 09-12-2004 à 16:12:50    

Taz a écrit :

j'ai rien compris, si tu lances ton appli en mode debug depuis ton ide, biensur que ça va bouffer plus.
 
Ton problème, c'est que tu as une différence d'occupation mémoire entre ton appli lancée sans debug dans ton IDE et quand tu lances l'appli tout seule ?


 
Non, c'est pas le mode debug, mais la release (qui possède une option de debug du type /debug en argument, et qui fait apparaitre une fenetre de log), lancée à la main.
Le pb, c'est que je comprends pas pourquoi quand je réduis la fenetre (ie, je clique sur le moins en haut a droite), imédiatement le GC libère 25 Mo.
 
Si il les libère, c'est qu'il en a pas besoin à cet instant. J'aimerais qu'il libère la mémoire non utilisée en permanance, en mode fenetré ou non fenétré, car 25 Mo non utilisé,c'est beaucoup. Or quand je fais des GC.Collect(), a priori bien placés, il ne libère rien (Il détruit bien mes objets, mais la quantité de RAM et de mémoire virtuelle utilisées ne changent pas). On reste à 26 Mo de ram utilisée.
 
Helloword, bjone : les 2Mo, ce sont ceux du working set, pas de la mémoire virtuelle (qui ne change pratiquement pas). Le working set passe de 26Mo à 2, on reste à 15 Mo pour la mémoire virtuelle.


Message édité par oliv5 le 09-12-2004 à 16:14:02
Reply

Marsh Posté le 10-12-2004 à 18:33:53    

up

Reply

Marsh Posté le 10-12-2004 à 21:28:39    

oliv5 a écrit :


J'ai essayé de placer des GC.Collect() à certains endroits, mais je ne vois strictement aucune différence, comme si cette instruction ne faisait rien.

sisi, normalement, on arrive à ralentir l'application avec ça. mais ça dépend, y'a des systèmes où elle est désactivée par défaut (donc elle ne ralentit rien).


---------------
trainoo.com, c'est fini
Reply

Marsh Posté le 10-12-2004 à 21:40:01    

:)
 
Ca je m'en doute, mais à priori, je l'ai ai placés aux bons endroits (quand il y a des libérations massives de ressources).
 
En fait, si je vois une action : le GC détruit bien les objets apres le GC.Collect(), car on passe dans leurs destructeurs. Mais, au niveau de la mémoire employée, rien, pas un mouvement.
 
C'est énervant, car je vois d'ici le client me dire : "Votre service, il est bien mais il est pas discret, il pompe trop de mémoire."

Reply

Marsh Posté le 10-12-2004 à 21:40:01   

Reply

Marsh Posté le 10-12-2004 à 21:51:23    

ben tu me donnes de l'argent et je regarde ce qu'on peut y faire à ton bordel. Et je t'explique les choses au fur et à mesure.


---------------
trainoo.com, c'est fini
Reply

Marsh Posté le 10-12-2004 à 22:50:00    

euh tu nous fais signe si t'arrives à faire tourner ton programme C# sur VM le tout avec 2Mo d'utilisation mémoire.

Reply

Marsh Posté le 10-12-2004 à 22:52:52    

Taz > la feinte c'est de mettre la bibliothèque de classe dans un espace mémoire (partagé par toutes les applications si possible) qui n'est pas pris en compte par l'outil de reporting de la consomation mémoire :p


---------------
trainoo.com, c'est fini
Reply

Marsh Posté le 10-12-2004 à 22:53:00    

Par curiosité, quels seraient tes tarifs pour un truc dans ce genre là ? (ie, optimiser une appli client-serveur + logiciel d'administration avec bdd ODBC, de la bonne programmation système de profondeur Win32)
 
De toute façons, je te le dis tout de suite, c'est un mauvais coup pour se faire de la thune : on bosse à 2, le contrat est rempli, mais le client nous embrouille sans cesse et on n'est pas d'accord sur les temps de développement nécéssaires pour faire "les petites(grosses) modifs" qu'il souhaite.
Et on a pas encore été payé pour le dev initial, d'ou la nécéssité de faire des versions de démo limités pour que le client puisse tester....
 
Bref, j'ai pas envie d'emmener un 3eme acteur dans ce bordel, quel qu'il soit.

Reply

Marsh Posté le 10-12-2004 à 22:57:09    

oliv5 > on peut discuter de ça en privé si tu veux.


---------------
trainoo.com, c'est fini
Reply

Marsh Posté le 10-12-2004 à 23:02:57    

Taz a écrit :

euh tu nous fais signe si t'arrives à faire tourner ton programme C# sur VM le tout avec 2Mo d'utilisation mémoire.


 
heu, voila mon signe.
 
Avant la reduction de la fenetre
http://olivkta.free.fr/temp/Avant.jpg
 
Apres la réduction de la fenetre
http://olivkta.free.fr/temp/Apres.jpg
 
La plupart du temps, ca remonte à 3-4 Mo dans les 10s qui suivent, puis dans la minute qui suit, de gros traitements se relancent et ca remonte encore bien sur, pour ne plus redescendre (alors que je libère bien ce qui ne sert plus).
 
edit : du jpg pour soulager votre BP.


Message édité par oliv5 le 10-12-2004 à 23:30:52
Reply

Marsh Posté le 10-12-2004 à 23:06:12    

oliv5 > en général (tous sauf celui d'Eiffel à ma conaissance), les GCs ne libèrement pas le tas de mémoire qu'ils ont piqué au système. Y'a une raison un peut technique à ça : l'épinglage des objets utilisés dans le code natif.
 
C'est une des raisons pour lequelles il faut absolument faire collaborer les GCs avec la mémoire virtuelle (l'autre raison, c'est le swapping).


---------------
trainoo.com, c'est fini
Reply

Marsh Posté le 10-12-2004 à 23:07:12    

oliv5> on peut avoir les titres des colones stp ? j'ai un gros doute d'un coup ....


---------------
trainoo.com, c'est fini
Reply

Marsh Posté le 10-12-2004 à 23:10:43    

des bmps, quelle bonne idée

Reply

Marsh Posté le 10-12-2004 à 23:11:30    

nraynaud a écrit :

oliv5> on peut avoir les titres des colones stp ? j'ai un gros doute d'un coup ....


 
que les 28 soient passé en swap ? [:ddr555]

Reply

Marsh Posté le 10-12-2004 à 23:13:57    

déjà commence par utiliser System.GC.GetTotalMemory()

Reply

Marsh Posté le 10-12-2004 à 23:16:39    

chrisbk a écrit :

que les 28 soient passé en swap ? [:ddr555]

y'a un 20Mo qui n'a pas bougé dans la colone d'à côté ...


---------------
trainoo.com, c'est fini
Reply

Marsh Posté le 10-12-2004 à 23:28:25    

j'arrive avec les colonnes :)
les 20Mo c'est la mémoire virtuelle.
 
Edit :
 
dsl pour les bmp, c'est fait à l'arrache.
Voila les colonnes associées :
 
http://olivkta.free.fr/temp/colonnes.jpg
 
 
System.GC.GetTotalMemory() : Extrait le nombre d'octets qu'il est actuellement prévu d'allouer.
 
Kesako ? ca va m'indiquer quoi ? (msdn pas clair là)

Reply

Marsh Posté le 10-12-2004 à 23:37:25    

heu, c'est louche la mémoire utilisée supérieure à la mémoire virtuelle utilisée, mais j'ai pareil sur mon PC aussi, y'a une explication à ça ?


---------------
trainoo.com, c'est fini
Reply

Marsh Posté le 10-12-2004 à 23:38:08    

nraynaud a écrit :

heu, c'est louche la mémoire utilisée supérieure à la mémoire virtuelle utilisée, mais j'ai pareil sur mon PC aussi, y'a une explication à ça ?


 
vi, t'additionnes les deux

Reply

Marsh Posté le 10-12-2004 à 23:38:46    

oui, tu lis l'aide et tu vois qu'il s'agirait des pages résidentes en mémoire.

Reply

Marsh Posté le 11-12-2004 à 00:29:08    

Utilise Process Explorer
http://www.sysinternals.com/ntw2k/ [...] cexp.shtml
View->Select Columns...->.Net
Tu as pas mal d'options intéressantes, comme le temps passé dans le GC, l'utilisation des 3 types de mémoire du GC, la mémoire allouée, ...


---------------
FAQ fclc++ - FAQ C++ - C++ FAQ Lite
Reply

Marsh Posté le 11-12-2004 à 00:58:03    

j'ai déjà essayé mais je vois pas grand chose bouger quand se produit soit le processus de garbage collection ou qd je réduis la fenetre.
 
Au passage, je vois pas ce que tu appelle les 3 types de mémoire du GC. (je vois, dans procexp, le tas, le working set et la mémoire virtuelle, mais bon c'est pas à ca que tu fais allusion non ?)
 
Tout ce que je vois, c'est que le "working set" (j'ai une vague idée de ce que c'est) passe de beaucoup de Mo (ca dépends qd on fait le test, mais au max ce sont 25 Mo) à 3 lorsque je réduis la fenetre. Le % de temps passé dans le GC, monte de 0.15 à 1 à ce moment là avant de retomber.
Le reste des paramètres (virtual size, heap bytes, allocated bytes/sec) reste stable.
 
En revanche qd se produit le GC.Collect(), ya rien qui bouge!
 
Un petite question : je vois que le tas (heap bytes) passe progressivement de 1Mo à 2, puis revient à 1. Le GC alloue les objets a cet endroit là ??? (je comprend plus rien, car si c'est le cas, pkoi ca bouge pas lors de la libération par gc.collect() ?

Reply

Marsh Posté le 11-12-2004 à 01:03:47    

t'as essayé System.GC.GetTotalMemory()  ?
 
parce que là on commence par se demander si ton problème ce n'est pas plutôt la lecture des informations et leur exactitudes.

Reply

Marsh Posté le 11-12-2004 à 01:23:44    

bah c'est des screenshots du task manager ça non? [:totozzz]

Reply

Marsh Posté le 11-12-2004 à 01:34:53    

oui "the real moins moins", c'est le taskmanager, tu veux celles de procexp ? (en jpg cette fois).
 
Edit : hop, vla les images
 
Avant la reduction de la fenetre :
http://olivkta.free.fr/temp/Avant2.jpg
 
Après :
http://olivkta.free.fr/temp/Apres2.jpg
 
(c'est la ligne en jaune)
 
Taz : je fous ca avant et apres GC.Collect() et je te dis ca. Pour l'exactitude des infos, je vous donne ce que je vois. J'aurais du mal a faire mieux, a part si j'ai raté une info qquepart.


Message édité par oliv5 le 11-12-2004 à 01:43:39
Reply

Marsh Posté le 11-12-2004 à 01:58:09    

Bon, voila un premier résultat :
 
Value 1 : 610596     2: 610596
 
1) c'est avant le GC.Collect()
2) c'est apres. A croire que ya rien a libéré. Pourtant juste avant, on ferme une socket, on détruits les objets associés (ie on les mets a null, ya pas d'autres références) et on vide un objet dataset :
 
monDataset.Clear();  
monDataset.AcceptChanges();
 
Comme il était plein d'informations diverses et variés (un tas de chaines de caractères), ca m'étonne que rien ne soit libéré.
 
Je vais en mettre ailleurs dans le code pour voir ce que ca dit. Mais je doute de ce GetTotalMemory(), car déjà, je pige pas la doc associée...
 
Edit: je verrais demain, il est tard là. bn tt le monde.


Message édité par oliv5 le 11-12-2004 à 01:58:39
Reply

Marsh Posté le 11-12-2004 à 03:22:09    

Citation :

Au passage, je vois pas ce que tu appelle les 3 types de mémoire du GC. (je vois, dans procexp, le tas, le working set et la mémoire virtuelle, mais bon c'est pas à ca que tu fais allusion non ?)


Non. Le GC sectorise les objets en 3 parties en fonction de leur durée de vie (résistance au garbage collecting). C'est expliqué dans l'article de Richter sur le GC:
http://msdn.microsoft.com/msdnmag/ [...] fault.aspx
et pour le 1°:
http://msdn.microsoft.com/msdnmag/ [...] fault.aspx
 
Depuis process explorer double clic sur le process, tu as tout le détail de sa mémoire dans l'onglet .Net (.Net CLR memory). Fait une comparaison des différents total mémoire utilisés avant/après.
 
Mais, après lecture complète du post...
Ne te fie pas à la colonne Utilisation Mémoire. Ton screenshot montre d'ailleurs que c'est naze, car elle dépasse la VM (même si la VM n'est pas la taille totale). C'est parce que ça prend en compte la mémoire partagée, c.a.d n'importe quelle dll mappée dans 2 process ou + et comptabilisée 2 fois ou +.
http://blogs.msdn.com/greggm/archi [...] 60078.aspx
Fie toi à la colonne VM memory, qui indique 20 Mo dans les 2 cas... Cette colonne représente la taille mémoire privée à ton process, et c'est pratiquement identique dans tes 2 exemples.
 
La colonne du working set size (Util. memoire) désigne la taille de la RAM physique accessible (et non pas réservée) à ton process, c'est à dire les pages physiques qu'il peut lire sans provoquer de fault.
Le working set est géré en accordéon par Windows, et cette gestion peut être influencée via SetProcessWorkingSetSize. Quand il a besoin de mémoire, Windows analyse les process et détermine ceux qui vont être swappés suivant différentes règles (idle depuis un certains temps, plus gros que les autres, ...). Il me parrait normal qu'un processus dont les fenêtres ont été minimisées soit considéré comme candidat au swapping.
Par contre j'avais pas noté que cet ajustement de working set (ou plutot sa forte diminution) était effectué en cas de minimisation de fenêtre, même si la mémoire est largement libre et que le swap est désactivé comme c'est le cas chez moi. Je pensais que c'était fait quand y'avait besoin de place, et apparement non, c'est pas tout à fait ça. Ca serait préventif, et surtout un working set brutalement diminué ne veut pas forcément dire que les pages ont été swappées et que leur prochain accès sera synonyme de page faults, mais juste qu'elles sont candidates à un swapping. Enfin c'est comme ça que j'interprète les choses.


---------------
FAQ fclc++ - FAQ C++ - C++ FAQ Lite
Reply

Marsh Posté le 11-12-2004 à 09:01:11    

bon, ben moi je vois plus de problème

Reply

Marsh Posté le 11-12-2004 à 18:04:15    

place tes using aux bons endroits (pas avant le namespace donc), ca appellera les dispose automatiquement des que les objets ne seront plus utiles...

Reply

Marsh Posté le 11-12-2004 à 18:33:03    

ToxicAvenger a écrit :

place tes using aux bons endroits (pas avant le namespace donc), ca appellera les dispose automatiquement des que les objets ne seront plus utiles...


 
Ca a une influence ? J'avoue que j'y connais rien. Qu'est ce que ca change ?
 

Code :
  1. using System.Text;
  2. namespace MonNamespace
  3. {
  4.   ...
  5. }


 

Code :
  1. namespace MonNamespace
  2. {
  3.    using System.Text;
  4.    ...
  5. }


 

Citation :

bon, ben moi je vois plus de problème.


 
Alors, ce serait normal. Voila ce que je comprend : quand je lance le client, il y a pas mal de mémoire de réservée en interne pour les traitements d'initialisation, donc pas mal de pages sont réservées.
Par la suite, on libère beaucoup des objets alloués initialement. Le GC passe derriere, détruit les objets, la mémoire occupée diminue mais le nombre de pages réservées reste identique.
 
Merci HelloWorld pour ces explications.

Reply

Marsh Posté le 11-12-2004 à 18:39:28    

using(truc machin=bidule() {
 
}

Reply

Marsh Posté le 11-12-2004 à 18:56:35    

Ha oui, c'est pas con, je vais voir si ca pourrait améliorer les choses. merci

Reply

Marsh Posté le 11-12-2004 à 20:12:39    

Citation :

Voila ce que je comprend : quand je lance le client, il y a pas mal de mémoire de réservée en interne pour les traitements d'initialisation, donc pas mal de pages sont réservées.


Quand tu lances le programme il détient la fenêtre qui a le focus, => c'est le thread prioritaire par rapport aux autres, et comme presque toute la mémoire est allouée au moment de son lancement, son working set suit la progression et la colonne Mem. utilisée traduit cette évolution. A ce moment, cette colonne & la colonne MV doivent se valoir.

Citation :

Par la suite, on libère beaucoup des objets alloués initialement. Le GC passe derriere, détruit les objets, la mémoire occupée diminue mais le nombre de pages réservées reste identique.


La mémoire diminue d'un point de vue .Net, mais pas forcément d'un point de vue Windows.
Y'a un truc que tu ne sembles pas avoir pigé : la mémoire collectée par le GC est libérée d'un point de vue .Net, mais pas forcément d'un point de vue systeme. On te l'a dit, un GC libére rarement la mémoire système qu'il a alloué. En pratique il le fait, mais par gros palliers. Si a chaque fois qu'un objet .Net était collecté et libérait 1Ko le GC les rendait au système, ça serait affreusement lent. Tu peux avoir totalement libéré la mémoire dans ton programme .Net, le GC conservera quand même les 10/20 Mo inutilisés.


Message édité par HelloWorld le 11-12-2004 à 20:17:22

---------------
FAQ fclc++ - FAQ C++ - C++ FAQ Lite
Reply

Marsh Posté le 11-12-2004 à 20:16:05    

Au passage nraynaud te l'a dit, appeler GC.Collect() peut effectivement libérer plus vite la mémoire (mais elle le sera de toute façons), mais ça se paye en temps d'exécution. L'opération de garbage collecting n'est pas gratuite. C'est expliqué dans l'article de Richter.


---------------
FAQ fclc++ - FAQ C++ - C++ FAQ Lite
Reply

Marsh Posté le    

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed