99 votes

L'exception 'System.OutOfMemoryException' a été levée alors qu'il y a encore beaucoup de mémoire libre.

Voici 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 sont libres. Lorsque je lance le programme, il y a clairement assez d'espace sur le PC pour gérer les 762 Mo de 100000000 nombres aléatoires. Je dois stocker autant de nombres aléatoires que possible compte tenu de la mémoire disponible. Lorsque je passerai à la production, il y aura 12 Go sur le boîtier et je veux les utiliser.

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

Mise à jour

J'ai pensé que le fait de diviser le problème en plus petits morceaux et d'ajouter progressivement à mes besoins en mémoire pourrait m'aider si le problème est dû à fragmentation de la mémoire mais ce n'est pas le cas. Je n'arrive pas à dépasser une taille totale de 256 Mo pour la liste des tableaux, même si je modifie la taille des blocs. .

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;

6 votes

Je vous recommande de réarchitecturer votre application afin de ne pas avoir à utiliser autant de mémoire. Que faites-vous pour avoir besoin de cent millions de chiffres en mémoire en même temps ?

2 votes

Vous n'avez pas désactivé votre pagefile ou quelque chose de stupide comme ça, n'est-ce pas ?

0 votes

@EricLippert, je rencontre ce problème en travaillant sur le problème P vs NP ( claymath.org/millenium-problems/p-vs-np-problem ). Avez-vous une suggestion pour réduire l'utilisation de la mémoire de travail (par exemple, en sérialisant et en stockant des blocs de données sur le disque dur, en utilisant le type de données C++, etc.)

149voto

Fredrik Mörk Points 85694

Vous voudrez peut-être lire ceci : " "Out Of Memory" ne fait pas référence à la mémoire physique " par Eric Lippert.

En bref, et de manière très simplifiée, "Out of memory" ne signifie pas vraiment que la quantité de mémoire disponible est trop faible. La raison la plus courante est que, dans l'espace d'adressage actuel, il n'existe pas de portion contiguë de mémoire suffisamment 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é comme une fichier massif sur le 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'a pas vraiment d'incidence sur ses performances.
  • "Le fait de manquer de mémoire vive entraîne rarement une erreur de type "out of memory". Au lieu d'une erreur, il en résulte de mauvaises performances car le coût total du fait que le stockage est en fait sur le disque devient soudainement pertinent.

0 votes

"Si vous avez 100 blocs de 4 Mo chacun, cela ne vous aidera pas lorsque vous aurez besoin d'un bloc de 5 Mo". - Je pense que ce serait mieux formulé avec une petite correction : "Si vous avez 100 "trou" blocs" .

26voto

Shay Erlichmen Points 23645

Vous ne disposez pas d'un bloc de mémoire continu pour allouer 762 Mo, votre mémoire est fragmentée et l'allocateur ne peut pas trouver 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 à un système d'exploitation 64 bits.
  3. Ou bien modifiez l'algorithme de façon à ce qu'il n'ait pas besoin d'un gros morceau de mémoire. Peut-être allouez-vous quelques morceaux de mémoire (relativement) plus petits.

9voto

Trisped Points 2571

Comme vous l'avez probablement compris, le problème est que vous essayez d'allouer un grand bloc contigu de mémoire, ce qui ne fonctionne pas en raison de la fragmentation de la mémoire. Si j'avais besoin de 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 indice particulier, il faut utiliser randomNumbers[i / sizeB][i % sizeB] .

Une autre option si vous accédez toujours aux valeurs dans l'ordre pourrait être d'utiliser le constructeur surchargé pour spécifier la graine. De cette façon, vous obtiendrez un nombre semi-aléatoire (comme la fonction DateTime.Now.Ticks ) la stocker dans une variable, puis, lorsque vous commencez à parcourir la liste, vous créez une nouvelle instance aléatoire en utilisant 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, si le blog dont le lien figure dans la réponse de Fredrik Mörk indique que le problème est généralement dû à un manque de espace adresse il n'énumère pas un certain nombre d'autres problèmes, comme la limitation de la taille des objets CLR à 2 Go (mentionnée dans un commentaire de ShuggyCoUk sur le même blog), passe sous silence la fragmentation de la mémoire, et ne mentionne pas l'impact de la taille des fichiers de page (et la façon dont il peut être traité avec l'utilisation de l'option de contrôle de la taille de la page). CreateFileMapping fonction ).

La limitation à 2 Go signifie que randomNumbers doit être inférieure à 2 Go. Comme les tableaux sont des classes et qu'ils ont eux-mêmes une certaine surcharge, cela signifie qu'un tableau de double devra être inférieure à 2^31. Je ne suis pas sûr de combien plus petite que 2^31 la longueur devrait être, mais Surcharge d'un tableau .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'adressage, mais lorsque vous créez et détruisez des objets, il y a des écarts entre les valeurs. Si ces espaces sont trop petits pour votre gros objet et qu'il est impossible de demander de l'espace supplémentaire, vous obtiendrez le message suivant System.OutOfMemoryException . Par exemple, si vous créez 2 millions d'objets de 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 0,6 Go de mémoire, mais celle-ci sera répartie dans l'espace d'adressage avec des blocs ouverts de 2024 octets entre les deux. Si vous deviez créer un objet de 0,2 Go, vous ne pourriez pas le faire parce qu'il n'y a pas de bloc assez grand pour l'accueillir et qu'il est impossible d'obtenir de l'espace supplémentaire (en supposant un environnement 32 bits). Les solutions possibles à ce problème sont des choses comme 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/prévenir la fragmentation de la mémoire. Il convient de noter qu'à moins que vous ne développiez un gros programme qui utilise une grande quantité de mémoire, ce problème ne se posera pas. En outre, ce problème peut se poser sur les systèmes 64 bits, car Windows est principalement limité par la taille du fichier de page 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 la taille du fichier de page. 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 limité par la RAM/le fichier de page et l'espace d'adressage du système d'exploitation.


C'était beaucoup plus long que prévu. J'espère que ça aidera quelqu'un. Je l'ai posté parce que j'ai rencontré le System.OutOfMemoryException Exécution d'un programme x64 sur un système doté de 24 Go de RAM, alors que mon tableau ne contenait que 2 Go de matériel.

0 votes

"Puisque la plupart des programmes demandent au système d'exploitation de la mémoire de travail et ne demandent pas de mappage de fichier" - Auriez-vous par hasard des ressources expliquant davantage ce point ? Lorsque les programmes ont besoin de leur ensemble de travail en mémoire (et sont limités par la taille de la RAM) et lorsque les programmes peuvent écrire sur le disque et sont limités par la mémoire contiguë (comme le suggère le blog de la réponse acceptée).

1 votes

@uMdRupert Cela fait un moment que j'ai fait des recherches et que j'ai écrit ceci, donc je n'ai pas de ressources supplémentaires. Avez-vous eu l'occasion de lire la [fonction CreateFileMapping] ( msdn.microsoft.com/fr/us/library/Windows/desktop/aa366537.aspx ) ? Vous pouvez également vous renseigner sur Paging .

5voto

jalf Points 142628

Je vous déconseille l'option de démarrage Windows /3GB. En dehors de tout le reste (c'est une surcharge de travail de faire cela pour un application qui se comporte mal, et cela ne résoudra probablement pas votre problème de toute façon), il peut causer beaucoup d'instabilité.

De nombreux pilotes Windows ne sont pas testés avec cette option, de sorte que beaucoup d'entre eux supposent que les pointeurs en mode utilisateur pointent toujours vers les 2 Go inférieurs de l'espace d'adressage. Ce qui signifie qu'ils peuvent se casser 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 d'adressage est déjà jonché de toutes sortes de données allouées. Il y a la pile, et tous les assemblages qui sont chargés, les variables statiques et ainsi de suite. Il n'y a aucune garantie qu'il y aura 800 Mo de mémoire contiguë non allouée quelque part.

L'allocation de 2 morceaux de 400 Mo serait probablement plus efficace. Ou 4 morceaux de 200 Mo. Il est beaucoup plus facile de trouver de la place pour des allocations plus petites dans un espace mémoire fragmenté.

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

0 votes

Le fait de diviser le travail en plus petits morceaux ne semble pas aider non plus, voir ma mise à jour ci-dessus.

2voto

Dejan Stanič Points 615

Si vous avez besoin de structures aussi importantes, 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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X