'System.OutOfMemoryException' a été levé alors qu'il reste encore beaucoup de mémoire libre

92

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 gratuits lorsque je démarre cette course, il y a clairement assez d'espace sur le PC pour gérer les 762 Mo de 100000000 nombres aléatoires. J'ai besoin de stocker autant de nombres aléatoires que possible compte tenu de la mémoire disponible. Lorsque je passerai en production, il y aura 12 Go sur la boîte et je veux en profiter.

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

Mettre à jour

Je pensais que diviser cela en petits morceaux et ajouter progressivement à mes besoins en mémoire aiderait si le problème était dû à la fragmentation de la mémoire , mais ce n'est pas le cas, je ne peux pas dépasser une taille totale de ArrayList de 256 Mo, peu importe ce que je fais en modifiant 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;
m3ntat
la source
6
Je recommanderais de rearchitechting votre application afin que vous n'ayez pas à utiliser autant de mémoire. Que faites-vous pour avoir besoin de cent millions de numéros en mémoire à la fois?
Eric Lippert
2
vous n'avez pas désactivé votre fichier d'échange ou quelque chose de stupide comme ça, n'est-ce pas?
jalf
@EricLippert, je rencontre cela lorsque je travaille 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, sérialisation et stockage de morceaux de données sur le disque dur, en utilisant le type de données C ++, etc.)
devinbost
@bosit c'est un site de questions et réponses. Si vous avez une question technique spécifique sur le code réel, postez-la sous forme de question.
Eric Lippert
@bostIT le lien pour le problème P vs NP dans votre commentaire n'est plus valide.
RBT

Réponses:

140

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

Bref, et très simplifié, «Out of memory» ne signifie pas vraiment que la quantité de mémoire disponible est trop petite. La raison la plus courante est que dans l'espace d'adressage actuel, il n'y a pas de partie 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 un fichier massif sur disque .
  • La RAM peut être considérée comme une simple optimisation des performances
  • La quantité totale de mémoire virtuelle que votre programme consomme n'est vraiment pas très pertinente pour ses performances
  • «manquer de RAM» entraîne rarement une erreur «manque de mémoire». Au lieu d'une erreur, cela entraîne de mauvaises performances, car le coût total du fait que le stockage est réellement sur disque devient soudainement pertinent.
Fredrik Mörk
la source
"Si vous avez 100 blocs, chacun de 4 Mo de large, cela ne vous aidera pas quand vous avez besoin d'un bloc de 5 Mo" - je pense qu'il serait mieux formulé avec une petite correction: "Si vous avez 100 blocs de" trous " " .
OfirD
31

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 plateforme: 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, donc leur espace d'adressage maximum 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 est alors de 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 baies dont la taille combinée sera supérieure à 2 Go, mais vous ne pouvez pas par défaut créer des baies de plus de 2 Go. Espérons que si vous souhaitez 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>
Technologie Shift
la source
Tu m'as sauvé. Je vous remercie!
Tee Zad Awk le
25

Vous n'avez pas de bloc continu de mémoire pour allouer 762 Mo, votre mémoire est fragmentée et l'allocateur ne peut pas trouver un trou assez grand pour allouer la mémoire nécessaire.

  1. Vous pouvez essayer de travailler avec / 3 Go (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'un gros morceau de mémoire. peut-être allouer quelques morceaux de mémoire plus petits (relativement).
Shay Erlichmen
la source
7

Comme vous l'avez probablement compris, le problème est que vous essayez d'allouer un gros bloc de mémoire contigu, 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 index particulier que 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 la valeur de départ. De cette façon, vous obtiendrez un nombre semi-aléatoire (comme le DateTime.Now.Ticks) stockez-le dans une variable, puis chaque fois que 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 lié à la réponse de Fredrik Mörk indique que le problème est généralement dû à un manque d' espace d'adressage, il ne répertorie pas un certain nombre d'autres problèmes, comme la limite de taille d'objet CLR de 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 du fichier d'échange (et comment il peut être résolu avec l'utilisation de la CreateFileMappingfonction ).

La limitation de 2 Go signifie qu'elle randomNumbers doit être inférieure à 2 Go. Puisque les tableaux sont des classes et ont eux-mêmes une surcharge, cela signifie qu'un tableau de doubledevra être plus petit que 2 ^ 31. Je ne sais pas à quel point la longueur devrait être inférieure à 2 ^ 31, mais les frais généraux 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 avoir 2 Go d'espace d'adressage, mais lorsque vous créez et détruisez des objets, il y aura des écarts entre les valeurs. Si ces espaces sont trop petits pour votre gros objet et qu'un espace supplémentaire ne peut pas être demandé, vous obtiendrez leSystem.OutOfMemoryException. Par exemple, si vous créez 2 millions d'objets de 1 024 octets, vous utilisez 1,9 Go. Si vous supprimez chaque objet dont l'adresse n'est pas un multiple de 3, vous utiliserez 0,6 Go de mémoire, mais il sera réparti dans l'espace d'adressage avec des blocs ouverts de 2024 octets entre les deux. Si vous avez besoin de créer un objet de 0,2 Go, vous ne pourrez pas le faire car il n'y a pas de bloc assez grand pour le ranger et un espace supplémentaire ne peut pas être obtenu (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 / empêcher la fragmentation de la mémoire. Il convient de noter qu'à moins que vous ne développiez un programme volumineux qui utilise une grande quantité de mémoire, cela ne sera pas un problème. Aussi,

É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 fichiers, ils seront limités par la RAM du système et la taille du fichier d'échange. Comme indiqué dans le commentaire de Néstor Sánchez (Néstor Sánchez) sur le blog, avec du code géré comme C #, vous êtes bloqué sur la limitation RAM / fichier de page et l'espace d'adressage du système d'exploitation.


C'était beaucoup plus long que prévu. Espérons que cela aide quelqu'un. Je l'ai posté parce que je suis tombé sur l' System.OutOfMemoryExceptionexécution d'un programme x64 sur un système avec 24 Go de RAM, même si mon tableau ne contenait que 2 Go de contenu.

Trisped
la source
5

Je déconseille l'option de démarrage Windows / 3GB. En dehors de tout le reste (c'est exagéré de faire ça pour un 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 ne sont pas testés avec cette option, donc plusieurs 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 casser horriblement avec / 3 Go.

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 assemblys chargés, les variables statiques, etc. Il n'y a aucune garantie qu'il y aura 800 Mo de mémoire contiguë non allouée n'importe où.

Allouer 2 blocs de 400 Mo serait probablement mieux. Ou 4 morceaux de 200 Mo. Des allocations plus petites sont beaucoup plus faciles à trouver dans un espace mémoire fragmenté.

Quoi qu'il en soit, si vous allez quand même déployer cela 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.

jalf
la source
Diviser le travail en plus petits morceaux ne semble pas non plus aider à voir ma mise à jour ci-dessus.
m3ntat
4

Le passage 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 porter.

Chris
la source
1

Windows 32 bits a une limite de mémoire de processus de 2 Go. L'option de démarrage / 3 Go que d'autres ont mentionnée rendra ces 3 Go avec seulement 1 Go restant pour l'utilisation du 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 nécessaire. Cela permet également de résoudre le problème selon lequel, même si vous disposez de 4 Go de RAM physique, l'espace d'adressage requis pour la carte vidéo peut rendre une partie importante de cette mémoire inutilisable - généralement environ 500 Mo.

redcalx
la source
1

Plutôt que d'allouer un tableau massif, pourriez-vous essayer d'utiliser un itérateur? Celles-ci sont exécutées en différé, ce qui signifie que les valeurs sont générées uniquement 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érera autant de nombres aléatoires que vous le souhaitez, mais ne les générera que lorsqu'ils sont demandés via une instruction foreach. Vous ne manquerez pas de mémoire de cette façon.

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

Judah Gabriel Himango
la source
Approche itinérante, mais je dois stocker autant que possible sous forme de référentiel de nombres aléatoires dans n'importe quel temps d'inactivité du reste de mon application, car cette application fonctionne sur une horloge de 24 heures prenant en charge plusieurs régions géographiques (plusieurs simulations de Monte Carlo), environ 70% de la charge de processeur maximale de jour, les temps restants tout au long de la journée, je veux mettre en mémoire tampon des nombres aléatoires dans tout l'espace mémoire libre. Le stockage sur disque est trop lent et annule en quelque sorte tous les gains que je pourrais faire en mémoire tampon dans ce cache mémoire à nombres aléatoires.
m3ntat
0

Eh bien, j'ai un problème similaire avec un grand ensemble de données 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 en petits morceaux si cela est possible. Parce que traiter autant de données, le problème reviendra tôt ou tard. De plus, vous ne pouvez pas connaître la configuration de chaque machine qui exécutera votre application, il y a donc toujours un risque que l'exception se produise sur un autre PC.

Francis B.
la source
En fait, je connais la configuration de la machine, cela fonctionne sur un seul serveur et je peux écrire ceci pour ces spécifications. Ceci est pour une simulation massive de Monte Carlo et j'essaie d'optimiser en tamponnant les nombres aléatoires à l'avance.
m3ntat
0

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

Ricardo Rix
la source
ne laissez pas le constructeur de cordes devenir si gros
Ricardo Rix
0

Convertissez votre solution en x64. Si vous rencontrez toujours un problème, accordez une longueur maximale à tout ce qui lève une exception comme ci-dessous:

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

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

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

Et puis construisez.

Si vous rencontrez toujours le problème:

Accédez à Projet-> Propriétés-> Événements de construction-> Ligne de commande d'é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.

Yasir Arafat
la source
-2

Augmentez la limite de processus Windows à 3 Go. (via boot.ini ou Vista boot manager)

leppie
la source
vraiment? quelle est la mémoire de processus maximale par défaut? Et comment le changer? Si je joue à un jeu ou à quelque chose sur mon PC, il peut facilement utiliser plus de 2 Go sur un seul EXE / processus, je ne pense pas que ce soit le problème ici.
m3ntat
/ 3 Go est excessif pour cela et peut causer beaucoup d'instabilité car de nombreux pilotes supposent que les pointeurs de l'espace utilisateur pointent toujours vers les 2 Go inférieurs.
jalf
1
m3ntat: Non, dans Windows 32 bits, un seul processus est limité à 2 Go. Les 2 Go restants de l'espace d'adressage sont utilisés par le noyau.
jalf