Gestion de la mémoire en .NET en 5 min

Partage de la mémoire en C#

La mémoire utilisée par un processus est divisée en 2 parties: la mémoire privée et la mémoire partagée.
Dans l’environnement .NET, le compilateur JIT compile le code pour chaque assembly séparément. Pour permettre le partage du code, il faut précompiler le code en utilisant NGEN. Ensuite il faut que les processus soient exécutés dans le même AppDomain. Enfin il faut indiquer au compilateur JIT que le code doit être partagé est décorant les fonctions avec l’attribut LoaderOptimization:

[LoaderOptimization(LoaderOptimization.MultiDomain)] 
static void Main(string[] args)

Jitter (JIT)

Le jitter (just-in-time) permet de transformer le code IL des applications en code assembleur. .NET est donc une technologie semi-interprétée. Il effectue cette compilation à la demande quand on fait appel aux assemblies. La compilation est effectuée une seule fois, à la première utilisation.

Points faibles

L’inconvénient majeur de cette technologie est que ça peut poser problème pour les applications qui ont une forte contrainte de démarrage rapide.

Multicore JIT

Depuis le framework 4.5, on va permettre d’optimiser le fonctionnement du JIT en étudiant son fonctionnement et d’enregistrer dans un fichier les décisions du jitter ainsi que la liste des fonctions à compiler. Ainsi au premier démarrage de l’application, le jitter sera optimisé et démarrera plus rapidement en lançant la compilation de certaines assemblies sur tous les processeurs.
Pour effectuer ce traitement, il suffit de rajouter dans le main:

ProfileOptimization.SetProfileRoot(@"C:\Startup"); 
ProfileOptimization.StartProfile("Startup.Profile");

Compiler le code en avance et MPGO

On peut compiler le code en avance en utilisant NGEN.exe. Cependant quand on compile en avance, le code est moins performant que s’il était compilé par le jitter. Ceci s’explique par le fait qu’à l’exécution, le jitter sait quelles sont les assemblies pour lesquelles il faut faire des optimisations puisque qu’il effectue ces modifications à la volée. Le Framework 4.5 permet de répondre à ce problème:
– en étudiant le fonctionnement du jitter pendant une exécution,
– en fournissant à NGEN.exe le résultat de cette étude,
– en compilant en avance en utilisant NGEN.exe.
Le composant qui permet cette optimisation est MPGO: Managed Profile Guided Optimization.
Pour l’utiliser:

Mpgo.exe -scenario Toto.exe -OutDir .\Data -AssemblyList Toto.exe

Pour l’instant l’optimisation faite par MPGO n’est pas au niveau de celle du jitter mais elle va s’améliorer dans les versions futures. L’optimization qui est faite actuellement permet de placer dans les mêmes pages mémoire des fonctions et des données qui s’appellent souvent entre elles (évite de faire appel à des objets dans des pages mémoire différentes qui peuvent éventuellement être swapées etc…).

Gestion des objets pour le Garbage Collector

Suivant leur taille, les objets sont gérés différemment par le Garbage Collector de .NET:
– si l’objet a une taille inférieure à 85000 bytes: il sera placé dans la pile CLR. Les piles de ce type sont compactées de temps en temps, ce qui entraîne Windows a stocké ces objets dans la mémoire physique. Ainsi on intervient directement dans la mémoire physique. Donc si l’application possède beaucoup de petits objets, on va beaucoup intervenir dans la mémoire physique sans utiliser la pile. Il faut donc éviter d’utiliser de petits objets.
– si l’objet a une taille supérieure à 85000 bytes: cet objet est placé dans la pile spécialisée pour les objets larges et il ne sera jamais déplacé par le Garbage Collector. Ainsi on ne va jamais intervenir dans la mémoire physique tant que l’objet ne dépasse pas 2gb pour un processus 32 bits ou que la page soit pleine.

http://geekswithblogs.net/akraus1/archive/2008/11/30/127475.aspx

Pile et tas managé

Les termes anglais sont “Stack” pour pile et “Managed Heap” pour tas managé.

Ces deux structures sont utilisées pour stocker:
pile: les variables locales (stockage par valeur pour les types valeur comme les struct et les énumérations) mais aussi les adresses vers les objets stockés dans le tas managé. Les adresses sont des variables référence.
tas managé: les instances de classe.

Pile (stack)

Un espace dans la pile est alloué lorsqu’on entre dans une méthode.
Les variables sont stockées dans cette espace pendant l’exécution de la méthode. Quand on sort de la méthode, l’espace est supprimé et les variables qui s’y trouvent sont perdues (seules les variables de type valeur et les références vers les instances dans le tas managé sont stockés dans la pile).

Passage de variables dans une fonction

Quand on passe une variable dans une fonction:
les variables de type valeur sont copiés dans un nouvelle espace de la pile. Quand cet espace est supprimé, les variables sont perdues.
pour les variables de type référence, seules les références vers les instances sont copiées. Quand l’espace alloué à la fonction est supprimé, les instances dans le tas managé ne sont pas supprimées.

Tas managé (managed heap)

Il contient les instances des objets de type référence. Comme son nom l’indique, la libération des objets dans cette structure est gérée par le garbage collector.

http://www.c-sharpcorner.com/UploadFile/rmcochran/csharp_memory01122006130034PM/csharp_memory.aspx

Leave a Reply