web-dev-qa-db-fra.com

'System.OutOfMemoryException' a été lancé lorsqu'il reste encore beaucoup de mémoire libre

Ceci est mon code:

int size = 100000000;
double sizeInMegabytes = (size * 8.0) / 1024.0 / 1024.0; //762 mb
double[] randomNumbers = new double[size];

Exception: Une exception de type 'System.OutOfMemoryException' a été levée.

J'ai 4 Go de mémoire sur cette machine 2,5 Go est gratuit lorsque je commence à l'utiliser, l'espace disponible sur l'ordinateur est suffisant pour gérer les 762 Mo de 1 000 000 de nombres aléatoires. J'ai besoin de stocker autant de nombres aléatoires que possible compte tenu de la mémoire disponible. Quand je vais en production, il y aura 12 Go sur la boîte et je veux l'utiliser.

Le CLR me contraint-il à une mémoire maximale par défaut pour commencer? et comment puis-je demander plus?

Mettre à jour

Je pensais que diviser cela en fragments plus petits et ajouter progressivement à mes besoins en mémoire aiderait si le problème était dû à fragmentation de la mémoire , mais ce n'est pas je ne peux pas dépasser une taille totale de ArrayList de 256 Mo indépendamment de ce que je fais en ajustant blockSize .

private static IRandomGenerator rnd = new MersenneTwister();
private static IDistribution dist = new DiscreteNormalDistribution(1048576);
private static List<double> ndRandomNumbers = new List<double>();

private static void AddNDRandomNumbers(int numberOfRandomNumbers) {
    for (int i = 0; i < numberOfRandomNumbers; i++) {
      ndRandomNumbers.Add(dist.ICDF(rnd.nextUniform()));                
  }
}

De ma méthode principale:

int blockSize = 1000000;

while (true) {
  try
  {
    AddNDRandomNumbers(blockSize);                    
  }
  catch (System.OutOfMemoryException ex)
  {
    break;
  }
}            
double arrayTotalSizeInMegabytes = (ndRandomNumbers.Count * 8.0) / 1024.0 / 1024.0;
81
m3ntat

Vous voudrez peut-être lire ceci: " " Mémoire insuffisante "ne fait pas référence à la mémoire physique " de Eric Lippert.

En résumé, et très simplifié, "Mémoire insuffisante" ne signifie pas vraiment que la quantité de mémoire disponible est trop petite. La raison la plus courante est qu’au sein de l’espace adresse actuel, aucune partie de la mémoire contiguë n’est assez grande pour servir l’allocation souhaitée. Si vous avez 100 blocs de 4 Mo chacun, cela ne vous aidera pas lorsque vous aurez besoin d'un bloc de 5 Mo.

Points clés: 

  • le stockage de données que nous appelons «mémoire de processus» est à mon avis mieux visualisé sous la forme d’un fichier massive sur disque.
  • La RAM peut être considérée comme une simple optimisation des performances
  • La quantité totale de mémoire virtuelle consommée par votre programme n’est vraiment pas très importante pour ses performances.
  • "manquer de RAM" entraîne rarement une erreur de «manque de mémoire». Au lieu d'une erreur, les performances sont mauvaises car le coût total du stockage sur disque devient soudainement pertinent.
125
Fredrik Mörk

Vous ne disposez pas d'un bloc de mémoire continu pour pouvoir allouer 762 Mo, votre mémoire est fragmentée et l'allocateur ne trouve pas un trou suffisamment grand pour allouer la mémoire nécessaire. 

  1. Vous pouvez essayer de travailler avec/3GB (comme d'autres l'ont suggéré)
  2. Ou passez au système d'exploitation 64 bits.
  3. Ou modifiez l'algorithme pour qu'il n'ait pas besoin d'une grande quantité de mémoire. peut-être allouer quelques plus petits morceaux (relativement) de mémoire.
25
Shay Erlichmen

Vérifiez que vous créez un processus 64 bits et non un processus 32 bits, qui est le mode de compilation par défaut de Visual Studio. Pour ce faire, faites un clic droit sur votre projet, Propriétés -> Construire -> cible de la plate-forme: x64. Comme tout processus 32 bits, les applications Visual Studio compilées en 32 bits ont une limite de mémoire virtuelle de 2 Go.

Les processus 64 bits n'ont pas cette limitation, car ils utilisent des pointeurs 64 bits. Par conséquent, leur espace d'adressage maximal théorique (la taille de leur mémoire virtuelle) est de 16 exaoctets (2 ^ 64). En réalité, Windows x64 limite la mémoire virtuelle des processus à 8 To. La solution au problème de limite de mémoire consiste alors à compiler en 64 bits.

Cependant, la taille de l’objet dans Visual Studio est toujours limitée à 2 Go par défaut. Vous pourrez créer plusieurs tableaux dont la taille combinée sera supérieure à 2 Go, mais vous ne pouvez pas créer par défaut de tableaux de plus de 2 Go. Espérons que si vous voulez toujours créer des tableaux de plus de 2 Go, vous pouvez le faire en ajoutant le code suivant à votre fichier app.config:

<configuration>
  <runtime>
    <gcAllowVeryLargeObjects enabled="true" />
  </runtime>
</configuration>
22
Shift Technology

Comme vous l'avez probablement compris, le problème est que vous essayez d'allouer un grand bloc de mémoire contigu, ce qui ne fonctionne pas en raison de la fragmentation de la mémoire. Si je devais faire ce que vous faites, je ferais ce qui suit:

int sizeA = 10000,
    sizeB = 10000;
double sizeInMegabytes = (sizeA * sizeB * 8.0) / 1024.0 / 1024.0; //762 mb
double[][] randomNumbers = new double[sizeA][];
for (int i = 0; i < randomNumbers.Length; i++)
{
    randomNumbers[i] = new double[sizeB];
}

Ensuite, pour obtenir un index particulier, vous utiliseriez randomNumbers[i / sizeB][i % sizeB].

Une autre option si vous accédez toujours aux valeurs dans l'ordre peut être d'utiliser le constructeur surchargé pour spécifier le germe. De cette façon, vous obtiendrez un nombre semi-aléatoire (comme le DateTime.Now.Ticks ) dans une variable, puis, chaque fois que vous commencerez à parcourir la liste, vous créerez une nouvelle instance Aléatoire à l'aide de la graine d'origine:

private static int randSeed = (int)DateTime.Now.Ticks;  //Must stay the same unless you want to get different random numbers.
private static Random GetNewRandomIterator()
{
    return new Random(randSeed);
}

Il est important de noter que même si le blog lié dans la réponse de Fredrik Mörk indique que le problème est généralement dû à un manque d'espace address, il ne répertorie pas un certain nombre d'autres problèmes, tels que la limitation de la taille des objets CLR de 2 Go dans un commentaire de ShuggyCoUk sur le même blog), passe en revue la fragmentation de la mémoire et omet de mentionner l’impact de la taille du fichier de page (et comment il peut être résolu avec l’utilisation de la fonction CreateFileMapping ).

La limite de 2 Go signifie que randomNumbers doit être inférieur à 2 Go. Étant donné que les tableaux sont des classes et ont eux-mêmes une surcharge, cela signifie qu'un tableau de double devra être plus petit que 2 ^ 31. Je ne sais pas combien de moins que 2 ^ 31 la longueur devrait être, mais Overhead d'un tableau de .NET? indique 12 à 16 octets.

La fragmentation de la mémoire est très similaire à la fragmentation du disque dur. Vous pouvez disposer de 2 Go d’espace d’adresse, mais au fur et à mesure que vous créez et détruisez des objets, des espaces seront créés entre les valeurs. Si ces espaces sont trop petits pour votre objet de grande taille et que vous ne pouvez pas demander d'espace supplémentaire, vous obtiendrez le System.OutOfMemoryException. Par exemple, si vous créez 2 millions d'objets 1024 octets, vous utilisez 1,9 Go. Si vous supprimez tous les objets dont l'adresse n'est pas un multiple de 3, vous utiliserez alors 6,6 Go de mémoire, mais celle-ci sera répartie sur l'espace d'adressage avec des blocs ouverts de 2 024 octets entre eux. Si vous devez créer un objet de format .2GB, vous ne pourrez pas le faire car il n’existe pas de bloc suffisamment grand pour y être inséré et il est impossible d’obtenir un espace supplémentaire (en supposant un environnement 32 bits). Les solutions possibles à ce problème sont notamment l'utilisation d'objets plus petits, la réduction de la quantité de données que vous stockez en mémoire ou l'utilisation d'un algorithme de gestion de la mémoire pour limiter/empêcher la fragmentation de la mémoire. Notez que si vous ne développez pas un programme volumineux qui utilise beaucoup de mémoire, cela ne posera pas de problème. En outre, ce problème peut survenir sur les systèmes 64 bits, car Windows est principalement limité par la taille du fichier d'échange et la quantité de RAM sur le système.

Étant donné que la plupart des programmes demandent de la mémoire de travail au système d'exploitation et ne demandent pas de mappage de fichier, ils seront limités par la RAM du système et par la taille du fichier d'échange. Comme indiqué dans le commentaire de Néstor Sánchez (Néstor Sánchez) sur le blog, avec un code géré comme C #, vous êtes bloqué par la limitation du fichier RAM/page et par l'espace d'adressage du système d'exploitation.


C'était bien plus long que prévu. Espérons que cela aide quelqu'un. Je l'ai postée parce que je rencontrais le System.OutOfMemoryException exécutant un programme x64 sur un système doté de 24 Go de RAM alors que mon tableau ne contenait que 2 Go de contenu.

7
Trisped

Je déconseillerais l'option de démarrage Windows/3GB. En dehors de tout le reste (c'est exagéré de faire cela pour n = application mal comportée, et cela ne résoudra probablement pas votre problème de toute façon), cela peut causer beaucoup d'instabilité.

De nombreux pilotes Windows n'étant pas testés avec cette option, bon nombre d'entre eux supposent que les pointeurs en mode utilisateur pointent toujours vers les 2 Go les plus bas de l'espace d'adressage. Ce qui signifie qu’ils risquent de se briser horriblement avec/3GB.

Cependant, Windows limite normalement un processus 32 bits à un espace d'adressage de 2 Go. Mais cela ne signifie pas que vous devez vous attendre à pouvoir allouer 2 Go!

L'espace adresse est déjà encombré de toutes sortes de données allouées. Il y a la pile et tous les assemblys chargés, les variables statiques, etc. Rien ne garantit qu’il y aura 800 Mo de mémoire contiguë non allouée n’importe où.

L'attribution de 2 400 Mo de morceaux serait probablement mieux lotie. Ou 4 morceaux de 200MB. Les petites allocations sont beaucoup plus faciles à trouver de la place dans un espace mémoire fragmenté.

Quoi qu'il en soit, si vous souhaitez le déployer sur une machine de 12 Go, vous devrez l'exécuter en tant qu'application 64 bits, ce qui devrait résoudre tous les problèmes.

5
jalf

Changer de 32 à 64 bits a fonctionné pour moi - cela vaut la peine d'essayer si vous êtes sur un PC 64 bits et qu'il n'a pas besoin de port.

3
chris

Si vous avez besoin de telles structures volumineuses, vous pourriez peut-être utiliser des fichiers mappés en mémoire . Cet article pourrait s'avérer utile: http://www.codeproject.com/KB/recipes/MemoryMappedGenericArray.aspx

LP, Dejan

2
Dejan Stanič

Plutôt que d'allouer un tableau massif, pourriez-vous utiliser un itérateur? Celles-ci sont exécutées avec retard, ce qui signifie que les valeurs ne sont générées que lorsqu'elles sont demandées dans une instruction foreach; vous ne devriez pas manquer de mémoire de cette façon:

private static IEnumerable<double> MakeRandomNumbers(int numberOfRandomNumbers) 
{
    for (int i = 0; i < numberOfRandomNumbers; i++)
    {
        yield return randomGenerator.GetAnotherRandomNumber();
    }
}


...

// Hooray, we won't run out of memory!
foreach(var number in MakeRandomNumbers(int.MaxValue))
{
    Console.WriteLine(number);
}

Ce qui précède génère autant de nombres aléatoires que vous le souhaitez, mais ne les générez que tels qu'ils sont demandés via une instruction foreach. Vous ne manquerez pas de mémoire de cette façon.

Sinon, si vous devez les avoir tous au même endroit, stockez-les dans un fichier plutôt que dans la mémoire.

1

Les fenêtres 32 bits ont une limite de mémoire de processus de 2 Go. L’option d’amorçage/3GB mentionnée par d’autres fera de cette mémoire 3 Go avec seulement 1 Go restant pour le noyau du système d’exploitation. De manière réaliste, si vous souhaitez utiliser plus de 2 Go sans tracas, un système d'exploitation 64 bits est requis. Cela résout également le problème suivant: bien que vous puissiez disposer de 4 Go de RAM physique, l'espace d'adressage requis pour la carte vidéo peut rendre une grande partie de cette mémoire inutilisable, généralement autour de 500 Mo.

1
redcalx

Essayez de définir la valeur de gcAllowVeryObjects propriété sur True dans le fichier appliction configuration .

Attention Avant d'activer cette fonctionnalité, assurez-vous que votre application n'inclut pas de code non sécurisé supposant que toutes les baies aient une taille inférieure à 2 Go. Par exemple, un code non sécurisé qui utilise des tableaux en tant que tampons peut être dépassé s'il est écrit en supposant que les tableaux ne dépassent pas 2 Go.

<configuration>  
  <runtime>  
    <gcAllowVeryLargeObjects enabled="true" />  
  </runtime>  
</configuration>
0
Mohamad-Al-Ibrahim

Convertissez votre solution en x64. Si vous rencontrez toujours un problème, accordez max length à tout ce qui génère une exception comme ci-dessous:

 var jsSerializer = new JavaScriptSerializer();
 jsSerializer.MaxJsonLength = Int32.MaxValue;
0
Samidjo

J'ai eu un problème similaire, il était dû à un StringBuilder.ToString ();

0
Ricardo Rix

Si vous n'avez pas besoin du processus d'hébergement Visual Studio:

Décochez l'option: Projet-> Propriétés-> Débogage-> Activer le processus d'hébergement Visual Studio

Et ensuite construire.

Si vous faites toujours face au problème:

Allez à Projet-> Propriétés-> Construire des événements-> Ligne de commande de l'événement post-construction et collez ce qui suit:

call "$(DevEnvDir)..\..\vc\vcvarsall.bat" x86
"$(DevEnvDir)..\..\vc\bin\EditBin.exe" "$(TargetPath)"  /LARGEADDRESSAWARE

Maintenant, construisez le projet.

0
Yasir Arafat

Eh bien, j'ai un problème similaire avec un ensemble de données volumineux et essayer de forcer l'application à utiliser autant de données n'est pas vraiment la bonne option. Le meilleur conseil que je puisse vous donner est de traiter vos données par petits morceaux si cela est possible. Parce que traiter beaucoup de données, le problème reviendra tôt ou tard. De plus, vous ne pouvez pas connaître la configuration de chaque ordinateur qui exécutera votre application, il y a donc toujours un risque que l'exception se produise sur un autre ordinateur.

0
Francis B.