"System.OutOfMemoryException" è stato generato quando è ancora disponibile molta memoria


92

Questo è il mio codice:

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

Eccezione: è stata generata un'eccezione di tipo "System.OutOfMemoryException".

Ho 4 GB di memoria su questa macchina 2,5 GB sono liberi quando avvio questo funzionamento, c'è chiaramente abbastanza spazio sul PC per gestire i 762 MB di 100000000 numeri casuali. Ho bisogno di memorizzare quanti più numeri casuali possibile data la memoria disponibile. Quando vado in produzione ci saranno 12 GB sulla confezione e voglio usarli.

Il CLR mi limita a una memoria massima predefinita con cui iniziare? e come chiedo di più?

Aggiornare

Ho pensato che suddividerlo in blocchi più piccoli e aggiungere in modo incrementale ai miei requisiti di memoria sarebbe stato utile se il problema fosse dovuto alla frammentazione della memoria , ma non è possibile superare una dimensione totale di ArrayList di 256 MB indipendentemente da ciò che faccio modificando 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()));                
  }
}

Dal mio metodo 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
Consiglierei di ripensare la tua applicazione in modo da non dover usare così tanta memoria. Cosa stai facendo che ti servono cento milioni di numeri tutti in memoria in una volta?
Eric Lippert

2
non hai disabilitato il tuo file di paging o qualcosa di stupido del genere, vero?
jalf

@EricLippert, mi imbatto in questo quando lavoro sul problema P vs. NP ( claymath.org/millenium-problems/p-vs-np-problem ). Hai un suggerimento per ridurre l'utilizzo della memoria di lavoro? (es. Serializzazione e archiviazione di blocchi di dati su disco rigido, utilizzando il tipo di dati C ++, ecc.)
devinbost

@bosit questo è un sito di domande e risposte. Se hai una domanda tecnica specifica sul codice effettivo, pubblicala come domanda.
Eric Lippert

@bostIT il link per il problema P vs. NP nel tuo commento non è più valido.
RBT

Risposte:


140

Potresti leggere questo: " " Memoria esaurita "non fa riferimento alla memoria fisica " di Eric Lippert.

In breve, e molto semplificato, "Memoria esaurita" non significa realmente che la quantità di memoria disponibile sia troppo piccola. Il motivo più comune è che all'interno dello spazio degli indirizzi corrente, non esiste una porzione contigua di memoria sufficientemente grande da servire l'allocazione desiderata. Se hai 100 blocchi, ciascuno grande 4 MB, questo non ti aiuterà quando avrai bisogno di un blocco da 5 MB.

Punti chiave:

  • la memorizzazione dei dati che chiamiamo "memoria di processo" è a mio parere visualizzata al meglio come un enorme file su disco .
  • La RAM può essere vista semplicemente come un'ottimizzazione delle prestazioni
  • La quantità totale di memoria virtuale consumata dal programma non è molto rilevante per le sue prestazioni
  • "L'esaurimento della RAM" raramente genera un errore di "memoria insufficiente". Invece di un errore, si traduce in cattive prestazioni perché il costo totale del fatto che l'archiviazione è effettivamente su disco diventa improvvisamente rilevante.

"Se si dispone di 100 blocchi, ogni 4 MB di grandi dimensioni, che non sta andando per aiutarvi a quando avete bisogno di un blocco 5 MB" - penso che sarebbe meglio formulata con una piccola correzione: "Se si dispone di 100 '' buco blocchi" .
OfirD

31

Verificare di creare un processo a 64 bit e non a 32 bit, che è la modalità di compilazione predefinita di Visual Studio. Per fare ciò, fai clic con il pulsante destro del mouse sul tuo progetto, Proprietà -> Costruisci -> target piattaforma: x64. Come qualsiasi processo a 32 bit, le applicazioni Visual Studio compilate a 32 bit hanno un limite di memoria virtuale di 2 GB.

I processi a 64 bit non hanno questa limitazione, poiché utilizzano puntatori a 64 bit, quindi il loro spazio di indirizzi massimo teorico (la dimensione della loro memoria virtuale) è di 16 exabyte (2 ^ 64). In realtà, Windows x64 limita la memoria virtuale dei processi a 8 TB. La soluzione al problema del limite di memoria è quindi compilare a 64 bit.

Tuttavia, la dimensione dell'oggetto in Visual Studio è ancora limitata a 2 GB, per impostazione predefinita. Sarai in grado di creare diversi array la cui dimensione combinata sarà maggiore di 2 GB, ma per impostazione predefinita non puoi creare array più grandi di 2 GB. Si spera, se vuoi ancora creare array più grandi di 2 GB, puoi farlo aggiungendo il seguente codice al tuo file app.config:

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

Mi hai salvato. Grazie!
Tee Zad Awk,

25

Non hai un blocco continuo di memoria per allocare 762 MB, la tua memoria è frammentata e l'allocatore non riesce a trovare un buco abbastanza grande per allocare la memoria necessaria.

  1. Puoi provare a lavorare con / 3GB (come altri avevano suggerito)
  2. Oppure passa al sistema operativo a 64 bit.
  3. Oppure modifica l'algoritmo in modo che non richieda una grossa fetta di memoria. forse allocare alcuni pezzi più piccoli (relativamente) di memoria.

7

Come probabilmente hai capito, il problema è che stai cercando di allocare un grande blocco contiguo di memoria, che non funziona a causa della frammentazione della memoria. Se avessi bisogno di fare quello che stai facendo, farei quanto segue:

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];
}

Quindi, per ottenere un particolare indice che useresti randomNumbers[i / sizeB][i % sizeB] .

Un'altra opzione se si accede sempre ai valori in ordine potrebbe essere quella di utilizzare il costruttore sovraccarico per specificare il seme. In questo modo otterresti un numero semi casuale (come il DateTime.Now.Ticks) memorizzarlo in una variabile, quindi ogni volta che inizi a scorrere l'elenco creerai una nuova istanza casuale utilizzando il seme originale:

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);
}

È importante notare che mentre il blog collegato nella risposta di Fredrik Mörk indica che il problema è solitamente dovuto alla mancanza di spazio degli indirizzi , non elenca una serie di altri problemi, come la limitazione delle dimensioni degli oggetti CLR da 2 GB (menzionata in un commento da ShuggyCoUk sullo stesso blog), sorvola sulla frammentazione della memoria e non menziona l'impatto della dimensione del file di pagina (e come può essere affrontato con l'uso della CreateFileMappingfunzione ).

La limitazione di 2 GB significa che randomNumbers deve essere inferiore a 2 GB. Poiché gli array sono classi e hanno un po 'di overhead, ciò significa che un array di doubledovrà essere più piccolo di 2 ^ 31. Non sono sicuro di quanto dovrebbe essere più piccola di 2 ^ 31 la lunghezza, ma il sovraccarico di un array .NET? indica 12-16 byte.

La frammentazione della memoria è molto simile alla frammentazione dell'HDD. Potresti avere 2 GB di spazio degli indirizzi, ma quando crei e distruggi gli oggetti ci saranno degli spazi tra i valori. Se questi spazi sono troppo piccoli per il tuo oggetto di grandi dimensioni e non è possibile richiedere spazio aggiuntivo, otterrai il fileSystem.OutOfMemoryException. Ad esempio, se crei 2 milioni di oggetti da 1024 byte, stai utilizzando 1,9 GB. Se elimini ogni oggetto in cui l'indirizzo non è un multiplo di 3, utilizzerai 0,6 GB di memoria, ma verrà distribuito nello spazio degli indirizzi con blocchi aperti di 2024 byte in mezzo. Se hai bisogno di creare un oggetto che era .2GB non saresti in grado di farlo perché non c'è un blocco abbastanza grande per adattarlo e non è possibile ottenere spazio aggiuntivo (supponendo un ambiente a 32 bit). Possibili soluzioni a questo problema sono cose come l'utilizzo di oggetti più piccoli, la riduzione della quantità di dati archiviati in memoria o l'utilizzo di un algoritmo di gestione della memoria per limitare / prevenire la frammentazione della memoria. Va notato che, a meno che non si stia sviluppando un programma di grandi dimensioni che utilizza una grande quantità di memoria, questo non sarà un problema. Anche,

Poiché la maggior parte dei programmi richiede memoria di lavoro dal sistema operativo e non richiede una mappatura dei file, saranno limitati dalla RAM del sistema e dalle dimensioni del file di paging. Come notato nel commento di Néstor Sánchez (Néstor Sánchez) sul blog, con codice gestito come C # sei bloccato alla limitazione della RAM / file di pagina e allo spazio degli indirizzi del sistema operativo.


Era molto più lungo del previsto. Si spera che aiuti qualcuno. L'ho pubblicato perché mi sono imbattuto System.OutOfMemoryExceptionnell'esecuzione di un programma x64 su un sistema con 24 GB di RAM anche se il mio array conteneva solo 2 GB di materiale.


5

Sconsiglierei l'opzione di avvio di Windows / 3GB. A parte tutto il resto (è eccessivo farlo per uno un'applicazione si comportano male, e probabilmente non risolverà il problema in ogni caso), può causare un sacco di instabilità.

Molti driver Windows non vengono testati con questa opzione, quindi alcuni di essi presumono che i puntatori in modalità utente puntino sempre ai 2 GB inferiori dello spazio degli indirizzi. Il che significa che potrebbero rompersi in modo orribile con / 3 GB.

Tuttavia, Windows normalmente limita un processo a 32 bit a uno spazio di indirizzi di 2 GB. Ma questo non significa che dovresti aspettarti di essere in grado di allocare 2 GB!

Lo spazio degli indirizzi è già disseminato di tutti i tipi di dati allocati. C'è lo stack e tutti gli assembly caricati, variabili statiche e così via. Non c'è alcuna garanzia che ci saranno 800 MB di memoria contigua non allocata ovunque.

L'assegnazione di 2 blocchi da 400 MB probabilmente andrebbe meglio. O 4 blocchi da 200 MB. È molto più facile trovare spazio per allocazioni più piccole in uno spazio di memoria frammentato.

Ad ogni modo, se hai intenzione di distribuirlo comunque su una macchina da 12 GB, ti consigliamo di eseguirlo come un'applicazione a 64 bit, che dovrebbe risolvere tutti i problemi.


Dividere il lavoro in blocchi più piccoli non sembra aiutare nemmeno a vedere il mio aggiornamento sopra.
m3ntat

4

Il passaggio da 32 a 64 bit ha funzionato per me: vale la pena provare se si utilizza un PC a 64 bit e non è necessario il porting.



1

Windows a 32 bit ha un limite di memoria di processo di 2 GB. L'opzione di avvio / 3 GB menzionata da altri renderà questo 3 GB con solo 1 GB rimanente per l'uso del kernel del sistema operativo. Realisticamente, se si desidera utilizzare più di 2 GB senza problemi, è necessario un sistema operativo a 64 bit. Ciò supera anche il problema per cui, sebbene tu possa avere 4 GB di RAM fisica, lo spazio di indirizzi richiesto per la scheda video può rendere inutilizzabile una parte considerevole di quella memoria, di solito intorno ai 500 MB.


1

Piuttosto che allocare un array enorme, potresti provare a utilizzare un iteratore? Questi sono di esecuzione ritardata, il che significa che i valori vengono generati solo quando sono richiesti in un'istruzione foreach; non dovresti esaurire la memoria in questo modo:

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);
}

Quanto sopra genererà tutti i numeri casuali che desideri, ma li genererà solo quando vengono richiesti tramite un'istruzione foreach. Non rimarrai senza memoria in questo modo.

In alternativa, se è necessario averli tutti in un unico posto, archiviarli in un file anziché in memoria.


Approccio interessante, ma ho bisogno di archiviare il più possibile come repository di numeri casuali in qualsiasi tempo di inattività del resto della mia applicazione poiché questa app funziona su un orologio di 24 ore che supporta più regioni geografiche (più simulazioni di Monte Carlo), circa il 70% del giorno carico massimo della CPU, i tempi rimanenti durante il giorno voglio memorizzare numeri casuali in tutto lo spazio di memoria libero. La memorizzazione su disco è troppo lenta e in qualche modo sconfigge qualsiasi guadagno che potrei fare con il buffering in questa cache di memoria di numeri casuali.
m3ntat

0

Bene, ho avuto un problema simile con un set di dati di grandi dimensioni e provare a forzare l'applicazione a utilizzare così tanti dati non è davvero l'opzione giusta. Il miglior consiglio che posso darti è elaborare i tuoi dati in piccole parti, se possibile. Poiché trattando così tanti dati, il problema prima o poi si ripresenterà. Inoltre, non puoi conoscere la configurazione di ogni macchina che eseguirà la tua applicazione, quindi c'è sempre il rischio che l'eccezione si verifichi su un altro PC.


In realtà conosco la configurazione della macchina, questa è in esecuzione su un solo server e posso scriverla per quelle specifiche. Questo è per una massiccia simulazione di Monte Carlo e sto tentando di ottimizzare tamponando i numeri casuali in anticipo.
m3ntat

0

Ho avuto un problema simile, era dovuto a StringBuilder.ToString ();


non lasciare che lo stringbuilder diventi così grande
Ricardo Rix

0

Converti la tua soluzione in x64. Se il problema persiste, concedi la lunghezza massima a tutto ciò che genera un'eccezione come di seguito:

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

0

Se non hai bisogno del processo di hosting di Visual Studio:

Deseleziona l'opzione: Progetto-> Proprietà-> Debug-> Abilita il processo di hosting di Visual Studio

E poi costruisci.

Se il problema persiste:

Vai a Progetto-> Proprietà-> Eventi di compilazione-> Riga di comando evento post-compilazione e incolla quanto segue:

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

Ora crea il progetto.


-2

Aumenta il limite del processo di Windows a 3 GB. (tramite boot.ini o Vista boot manager)


veramente? qual è la memoria di processo massima predefinita? E come cambiarlo? Se gioco a un gioco o qualcosa del genere sul mio PC, posso facilmente utilizzare 2+ GB da un singolo EXE / processo, non credo che questo sia il problema qui.
m3ntat

/ 3GB è eccessivo per questo e può causare molta instabilità poiché molti driver presumono che i puntatori dello spazio utente puntino sempre ai 2 GB inferiori.
jalf

1
m3ntat: No, in Windows a 32 bit, un singolo processo è limitato a 2 GB. I restanti 2 GB di spazio degli indirizzi vengono utilizzati dal kernel.
jalf

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.