Optimisation mémoire C#

Il se pose souvent la question de l’optimisation de la mémoire quand on commence à coder des programmes gourmands ou des services qui ne s’arrêtent jamais, il en va de même pour les applications Web.

Le Framework de Microsoft, tout comme Java possède un garbage collector (ramasse miette) qui va gérer la mémoire pour vous. Ce travail est un travail de fond dont on n’a pas à se soucier 90% du temps. Il faut cependant avoir en tête que les ressources ne sont pas infinies est qu’un processus .net ne peut avoir que 60% (configuration machine.config) de la mémoire vive allouée à un processus par le système. Heureusement il est possible d’agir sur ce garbage collector et de l’aider à mieux gérer votre application.

Principe de base

Pour cela il est important de comprendre un principe de base du Framework, les ressources managées et les ressources non managées.

Ressources managées :

On entend par ressources managées toutes les variables dont le type est entièrement géré par le Framework (ex : Int32, String, StringBuilder, XmlDocument,…).

Ces ressources vont entièrement être la charge du Framework et il a la main sur la gestion mémoire de ces objets.

Ressources non managées :

On entend par ressources non managées toutes les ressources qui font appels à des intervenants extérieurs, en général le système d’exploitation (ex : les connexions base de donnée, le système de fichier, la couche graphique Windows, les bitmap basés sur GDI+).

Ces ressources possèdent tout le temps une méthode Dispose() qui permet d’appeler les méthode qui vont relâcher les objets non managés que le Framework ne va pas savoir gérer correctement.

Il est capitale d’appeler les méthodes dispose le plus rapidement possible dès qu’on a plus besoin de ses ressources et surtout de prévoir le cas ou on ne pourrait pas passer dans le code appelant le dispose suite à une exception avant cet appel. Plusieurs patterns permettent de garantir cela de façon optimal :

Le Try /Finally

Le using

Aider le Garbage Collector

Dispose /Destructeur et le garbage collector

Destructeur

Le garbage collector décide lui-même des objets managés qu’il peut détruire. Il prend la décision qu’un objet peut être détruit si plus aucun moyen d’accéder à cet objet n’est disponible dans le code au moment ou il regarde. Au moment de détruire l’objet il appelle le destructeur de la classe s’il en possède un.

– Une méthode Finalize ne doit pas lever d’exceptions, parce qu’elles ne peuvent pas être gérées par l’application et peuvent provoquer l’arrêt de cette dernière. En savoir plus

– L’implémentation des destructeurs ou des méthodes Finalize peut avoir un impact négatif sur les performances et il est préférable d’éviter de les utiliser inutilement. En savoir plus

Dispose

La méthode Dispose est quand a elle implémentée pour les utilisateurs de vos classes.

Il va de soit que le Destructeur et le Dispose doivent partager le même code

Dispose /Destructeur : Pourquoi, Ou, Quand, Comment

Pourquoi :

Coder une méthode pour libérer des ressources est exclusivement réservée aux ressources non managées et dans des rares cas à des objets managés si on veut être plus rapide que le garbage collector.

Ou :

Dans une méthode privée commune au Destructeur et au Dispose

Quand :

Dès qu’une de vos classes utilise une ressource non managée comme donnée privée à la classe.

En règle générale, toute classe qui manipule des objets non managés si elle ne Dispose pas immédiatement ces objets doit implémenter elle-même l’interface IDisposable afin de laisser la possibilité au développeur utilisant votre classe de Disposer le plus tôt possible et donner la possibilité au garbage collector d’effectuer son nettoyage correctement.

Comment :

En utilisant le pattern suivant ci-dessous.

// Design pattern for a base class.
public class Base: IDisposable
{
//Implement IDisposable.
 public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// Free other state (managed objects).
}
// Free your own state (unmanaged objects).
// Set large fields to null.
}

// Use C# destructor syntax for finalization code.
~Base()
{
// Simply call Dispose(false).
Dispose (false);
}
}

Une méthode Dispose doit appeler la méthode SuppressFinalize pour l’objet qu’il supprime. Si l’objet figure actuellement dans la file d’attente de finalisation, SuppressFinalize empêche l’appel de sa méthode Finalize. N’oubliez pas que l’exécution d’une méthode Finalize est une opération coûteuse. Si votre méthode Dispose a déjà procédé au nettoyage de l’objet, il est dans ce cas inutile que le garbage collector appelle la méthode Finalize de l’objet.

Gérer soit même la mémoire des objets managés

Il peut y a voir certains cas ou vous avez besoin de libérer le mémoire avant le garbage collector.

Exemple :

Vous travaillez sur une tache qui charge en mémoire dans une liste un tableau d’objets très volumineux. Le travail sur cette liste se termine. La tache effectue un autre travail très long et charge une seconde liste, vous pouvez rencontrer une exception de type ‘System.OutOfMemoryException’. Il faudrait donc pouvoir s’assurer que la première liste a été nettoyée par le garbage collector.

Le garbage collector

Le garbage collector met les objets en mémoire dans 4 espaces séparés suivant la classification suivante :

Génération 0 : tous les objets en mémoire fréquemment utilisés

Génération 1: tous les objets en mémoire peu utilisés provenant de la génération 0

Génération 2: tous les objets très peu utilisés et qui possèdent un destructeur

Large Heap : tous les objets dont la taille est supérieure à 85kb

GLes objets en Gen2 ne seront nettoyés que lorsqu’un nettoyage complet intervient ce qui est effectué par un thread indépendant du garbage collector. Cette opération peut être longue et couteuse en CPU d’où le fait d’être prudent quand on crée des objets avec un destructeur et d’être certain d’en avoir besoin.

Le GC ne va nettoyer que les objets inaccessible dans le code au moment ou il décide de nettoyer. Concrètement si une variable est encore accessible plus bas dans le code il ne va pas la nettoyer, même si vous l’avez mis à null. Cela s’appelle une référence forte. Vous pouvez lui indiquer qu’il s’agit d’une référence faible pour qu’il libère de suite, à vos risque et péril et à vous de réallouer cette variable ensuite. Pour arriver à ceci appliquer le pattern suivant :

En savoir plus

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s