Vers l’infini et au-delà

L’apparition des OS 64bits a permis de développer des applications consommant énormément de mémoire. Tout d’abord, la limitation à 2Go par processus est levée.

En effet, vous pouvez créer des applications dépassant largement cette limite (la seule limite actuelle restant votre machine). Pour connaitre votre limite selon votre version de Windows, je vous conseille de consulter cette page de Microsoft MSDN

Prenons un exemple, créons un projet console en 64bits :

project 64bits

Essayons de remplir la mémoire par bloc de 1Go environ :

List< int[]> myObjects = new List();
Console.WriteLine("Allocate several objects...");
for (int i = 0; i < GigaBytesRAMSize; i++)
{
   myObjects.Add(new int[(int)Math.Pow(1024, 3) / 4]);
}

Si l’on appelle ce code 6 fois en affichant l’état de la mémoire avant et après l’allocation, cela nous donne :

allocate several objects

On pourrait se dire que le mode 64bits est la réponse à tous les problèmes de mémoires.

Prenons un nouvel exemple, essayons de créer un objet de 10Go :

int nbElement = 50000;
Console.WriteLine("Allocate an array of {0}*{0} element(s)", nbElement);
int[,] MemoryKiller = new int[nbElement, nbElement];

Cela nous donne:

allocate very large object exception OutOfMemory

Comme on peut le constater, une exception OutOfMemoryException a été levée. Ceci est dû à une limitation sur la taille maximum pour l’allocation d’un objet. En effet il est impossible d’allouer un objet de plus de 2Go.

Cette limitation est désormais levée dans la version 4.5 du Framework et vous permet de dépasser (et largement) cette limite, mais attention ce n’est pas le comportement par défaut. Et là vous me dites : pourquoi mettre cette limitation alors qu’un processus 64bits est tout à fait capable de les allouer ? Je vous répondrai : vous avez déjà essayé de manipuler des objets de plus de 2Go ? Evidemment non, et heureusement car ceci n’est pas très performant. Néanmoins si vous n’avez pas le choix cette solution est pour vous.

La solution est très simple, ajoutez la section suivante dans votre fichier de config :

<configuration>
   <runtime>
      <gcAllowVeryLargeObjects enabled="true" />
   </runtime>
</configuration>

Nous voilà prêt à remplir la mémoire de notre processus.

Reprenons notre exemple précédent sans aucune modification :

allocate a very large object

Attention: cette solution a évidemment une limite: la taille de votre mémoire !

En effet, si vous avez désactivé votre fichier de pagination, comme moi, vous serez limité à la taille de votre mémoire physique (sinon la taille de votre mémoire physique + le fichier de pagination).

Chérie, ça passe ?

Comme nous l’avons vu précédemment, un manque de mémoire lève une exception de type OutOfMemoryException. Attraper cette exception permet de continuer le programme mais cela reste coûteux et peu élégant.

Il existe une alternative pour éviter cette exception, vérifier et réserver un espace mémoire : la classe MemoryFailPoint

Cette classe permet de vérifier la disponibilité d’une quantité de mémoire définie.

Prenons un nouvel exemple : une boucle infinie qui crée des objets de 1Go environ :

List myList = new List();
while (true)
{
   try
   {
      MemoryFailPoint mfp = new MemoryFailPoint(1200);
      myList.Add(new int[(int)Math.Pow(1024, 3) / 4]);
      mfp.Dispose();
   }
   catch (InsufficientMemoryException ex)
   {
      Console.WriteLine(ex.Message);
      break;
   }
}

Dans cet exemple on demande 1200 Mo, si l’espace mémoire est suffisant on continue, sinon une exception du type InsufficientMemoryException est levée :

mémoire insuffisante

Dans ce cas, pourquoi utiliser cette classe si l’on a toujours une exception qui est levée ?

Tout simplement car ici, la mémoire n’est pas encore allouée. Pour vérifier cela utilisons un outil issu de SysInternals : VMMap.

Voyons l’état de la mémoire après l’instanciation de l’objet de type MemoryFailPoint:

VMMap snapshot 1

Comme on peut le constater l’objet MemoryFailPoint n’alloue pas les 1200Mo demandés. En effet la « Managed Head » ne contient que 236K pour le processus courant.

Par contre l’instanciation de notre tableau d’entier consomme bien la mémoire attendue :

VMMap snapshot 2

Attention à bien libérer votre objet MemoryFailPoint, car sinon la mémoire n’est pas rendue au processus, exemple :


while (true)
{
   try
   {
      mfpList.Add(new MemoryFailPoint(1));
      loopNumber++;
   }
   catch (InsufficientMemoryException ex)
   {
      Console.WriteLine(ex.Message);
      break;
   }
}
Console.WriteLine("{0}MB \"reserved\"",loopNumber);

Ce qui nous donne:

mémoire insuffisante

Par contre les autres processus pourront tout à fait utiliser cette mémoire « réservée ».