'System.OutOfMemoryException' wurde ausgelöst, wenn noch genügend Speicher frei ist

92

Das ist mein Code:

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

Ausnahme: Ausnahme vom Typ 'System.OutOfMemoryException' wurde ausgelöst.

Ich habe 4 GB Speicher auf diesem Computer. 2,5 GB sind frei, wenn ich dies starte. Auf dem PC ist eindeutig genug Speicherplatz vorhanden, um die 762 MB mit 100000000 Zufallszahlen zu verarbeiten. Ich muss so viele Zufallszahlen wie möglich speichern, wenn Speicher verfügbar ist. Wenn ich zur Produktion gehe, sind 12 GB auf der Box und ich möchte davon Gebrauch machen.

Beschränkt mich die CLR zunächst auf einen Standard-Maximalspeicher? und wie fordere ich mehr an?

Aktualisieren

Ich dachte, dies in kleinere Teile aufzuteilen und meine Speicheranforderungen schrittweise zu erhöhen, würde helfen, wenn das Problem auf Speicherfragmentierung zurückzuführen ist , aber ich komme nicht über eine ArrayList-Gesamtgröße von 256 MB hinaus, unabhängig davon, was ich bei der Optimierung von blockSize mache .

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

Von meiner Hauptmethode:

int blockSize = 1000000;

while (true) {
  try
  {
    AddNDRandomNumbers(blockSize);                    
  }
  catch (System.OutOfMemoryException ex)
  {
    break;
  }
}            
double arrayTotalSizeInMegabytes = (ndRandomNumbers.Count * 8.0) / 1024.0 / 1024.0;
m3ntat
quelle
6
Ich würde empfehlen, Ihre Anwendung neu zu durchsuchen, damit Sie nicht so viel Speicher benötigen. Was machst du, dass du hundert Millionen Nummern auf einmal im Speicher brauchst?
Eric Lippert
2
Sie haben Ihre Auslagerungsdatei oder so etwas Dummes nicht deaktiviert, oder?
Jalf
@EricLippert, ich stoße darauf, wenn ich an dem P vs. NP-Problem arbeite ( Claymath.org/millenium-problems/p-vs-np-problem ). Haben Sie einen Vorschlag zur Reduzierung der Arbeitsspeicherauslastung? (zB Serialisierung und Speicherung von
Datenblöcken
@bosit Dies ist eine Frage- und Antwortseite. Wenn Sie eine bestimmte technische Frage zum tatsächlichen Code haben, stellen Sie diese als Frage.
Eric Lippert
@bostIT Der Link für das P vs. NP-Problem in Ihrem Kommentar ist nicht mehr gültig.
RBT

Antworten:

140

Vielleicht möchten Sie Folgendes lesen: " " Nicht genügend Speicher "bezieht sich nicht auf den physischen Speicher " von Eric Lippert.

Kurz gesagt und sehr vereinfacht bedeutet "Nicht genügend Speicher" nicht wirklich, dass die Menge des verfügbaren Speichers zu gering ist. Der häufigste Grund ist, dass im aktuellen Adressraum kein zusammenhängender Teil des Speichers vorhanden ist, der groß genug ist, um die gewünschte Zuordnung zu erfüllen. Wenn Sie 100 Blöcke mit jeweils 4 MB haben, hilft Ihnen das nicht, wenn Sie einen 5-MB-Block benötigen.

Wichtige Punkte:

  • Der Datenspeicher, den wir als "Prozessspeicher" bezeichnen, lässt sich meiner Meinung nach am besten als massive Datei auf der Festplatte darstellen .
  • RAM kann lediglich als Leistungsoptimierung angesehen werden
  • Die Gesamtmenge an virtuellem Speicher, die Ihr Programm belegt, ist für seine Leistung nicht besonders relevant
  • "Nicht genügend RAM" führt selten zu einem Fehler "Nicht genügend Speicher". Anstelle eines Fehlers führt dies zu einer schlechten Leistung, da die vollen Kosten der Tatsache, dass sich der Speicher tatsächlich auf der Festplatte befindet, plötzlich relevant werden.
Fredrik Mörk
quelle
31

Stellen Sie sicher, dass Sie einen 64-Bit-Prozess erstellen und keinen 32-Bit-Prozess. Dies ist der Standardkompilierungsmodus von Visual Studio. Klicken Sie dazu mit der rechten Maustaste auf Ihr Projekt, Eigenschaften -> Erstellen -> Plattformziel: x64. Wie bei jedem 32-Bit-Prozess haben in 32-Bit kompilierte Visual Studio-Anwendungen ein virtuelles Speicherlimit von 2 GB.

64-Bit-Prozesse unterliegen dieser Einschränkung nicht, da sie 64-Bit-Zeiger verwenden. Daher beträgt ihr theoretischer maximaler Adressraum (die Größe ihres virtuellen Speichers) 16 Exabyte (2 ^ 64). In Wirklichkeit beschränkt Windows x64 den virtuellen Speicher von Prozessen auf 8 TB. Die Lösung für das Problem der Speicherbeschränkung besteht dann darin, in 64-Bit zu kompilieren.

Die Objektgröße in Visual Studio ist jedoch standardmäßig weiterhin auf 2 GB beschränkt. Sie können mehrere Arrays erstellen, deren kombinierte Größe größer als 2 GB ist. Sie können jedoch standardmäßig keine Arrays erstellen, die größer als 2 GB sind. Wenn Sie dennoch Arrays mit mehr als 2 GB erstellen möchten, können Sie dies hoffentlich tun, indem Sie der Datei app.config den folgenden Code hinzufügen:

<configuration>
  <runtime>
    <gcAllowVeryLargeObjects enabled="true" />
  </runtime>
</configuration>
Schichttechnologie
quelle
Du hast mich gerettet. Danke dir!
Tee Zad Awk
25

Sie haben keinen kontinuierlichen Speicherblock, um 762 MB zuzuweisen, Ihr Speicher ist fragmentiert und der Allokator kann keine ausreichend große Lücke finden, um den benötigten Speicher zuzuweisen.

  1. Sie können versuchen, mit / 3GB zu arbeiten (wie andere vorgeschlagen hatten)
  2. Oder wechseln Sie zum 64-Bit-Betriebssystem.
  3. Oder ändern Sie den Algorithmus so, dass er keinen großen Speicherplatz benötigt. Weisen Sie möglicherweise ein paar kleinere (relativ) Speicherblöcke zu.
Shay Erlichmen
quelle
7

Wie Sie wahrscheinlich herausgefunden haben, besteht das Problem darin, dass Sie versuchen, einen großen zusammenhängenden Speicherblock zuzuweisen, der aufgrund der Speicherfragmentierung nicht funktioniert. Wenn ich tun müsste, was Sie tun, würde ich Folgendes tun:

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

Dann würden Sie einen bestimmten Index erhalten, den Sie verwenden würden randomNumbers[i / sizeB][i % sizeB].

Eine andere Option, wenn Sie immer der Reihe nach auf die Werte zugreifen, besteht darin, den Startwert mit dem überladenen Konstruktor anzugeben. Auf diese Weise erhalten Sie eine Halbzufallszahl (wie die DateTime.Now.Ticks), die in einer Variablen gespeichert ist. Wenn Sie dann die Liste durchgehen, erstellen Sie eine neue Zufallsinstanz mit dem ursprünglichen Startwert:

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

Es ist wichtig zu beachten, dass der in Fredrik Mörks Antwort verlinkte Blog zwar darauf hinweist, dass das Problem normalerweise auf einen Mangel an Adressraum zurückzuführen ist , jedoch keine Reihe anderer Probleme auflistet, wie z. B. die Beschränkung der CLR-Objektgröße auf 2 GB (in einem Kommentar von erwähnt) ShuggyCoUk im selben Blog) beschönigt die Speicherfragmentierung und erwähnt nicht die Auswirkungen der Auslagerungsdateigröße (und wie sie mithilfe der CreateFileMappingFunktion behoben werden kann ).

Die Beschränkung auf 2 GB bedeutet, randomNumbers dass weniger als 2 GB vorhanden sein müssen. Da Arrays Klassen sind und selbst einen gewissen Overhead haben, bedeutet dies, dass ein Array von doublekleiner als 2 ^ 31 sein muss. Ich bin nicht sicher, wie viel kleiner als 2 ^ 31 die Länge sein müsste, aber Overhead eines .NET-Arrays? zeigt 12 - 16 Bytes an.

Die Speicherfragmentierung ist der Festplattenfragmentierung sehr ähnlich. Möglicherweise verfügen Sie über 2 GB Adressraum. Beim Erstellen und Zerstören von Objekten treten jedoch Lücken zwischen den Werten auf. Wenn diese Lücken für Ihr großes Objekt zu klein sind und kein zusätzlicher Speicherplatz angefordert werden kann, erhalten Sie dieSystem.OutOfMemoryException. Wenn Sie beispielsweise 2 Millionen 1024-Byte-Objekte erstellen, verwenden Sie 1,9 GB. Wenn Sie jedes Objekt löschen, bei dem die Adresse kein Vielfaches von 3 ist, verwenden Sie 0,6 GB Speicher, der jedoch über den Adressraum verteilt ist und dazwischen offene Blöcke mit 2024 Byte enthält. Wenn Sie ein Objekt mit einer Größe von 0,2 GB erstellen müssen, können Sie dies nicht tun, da kein Block vorhanden ist, der groß genug ist, um ihn einzufügen, und kein zusätzlicher Speicherplatz verfügbar ist (unter der Annahme einer 32-Bit-Umgebung). Mögliche Lösungen für dieses Problem sind beispielsweise die Verwendung kleinerer Objekte, die Reduzierung der im Speicher gespeicherten Datenmenge oder die Verwendung eines Speicherverwaltungsalgorithmus zur Begrenzung / Verhinderung der Speicherfragmentierung. Es ist zu beachten, dass dies kein Problem darstellt, es sei denn, Sie entwickeln ein großes Programm, das viel Speicher benötigt. Ebenfalls,

Da die meisten Programme Arbeitsspeicher vom Betriebssystem anfordern und keine Dateizuordnung anfordern, werden sie durch den RAM und die Auslagerungsdateigröße des Systems begrenzt. Wie im Kommentar von Néstor Sánchez (Néstor Sánchez) im Blog erwähnt, bleiben Sie bei verwaltetem Code wie C # bei der Beschränkung der RAM- / Auslagerungsdatei und des Adressraums des Betriebssystems.


Das war viel länger als erwartet. Hoffentlich hilft es jemandem. Ich habe es gepostet, weil ich System.OutOfMemoryExceptionauf einem System mit 24 GB RAM auf das Ausführen eines x64-Programms gestoßen bin, obwohl mein Array nur 2 GB Material enthielt.

Trisped
quelle
5

Ich würde von der Windows-Boot-Option / 3GB abraten. Abgesehen von allem anderen (es ist übertrieben, dies für eine schlecht benommene Anwendung zu tun , und es wird Ihr Problem wahrscheinlich sowieso nicht lösen), kann es eine Menge Instabilität verursachen.

Viele Windows-Treiber werden mit dieser Option nicht getestet, daher gehen einige davon aus, dass Zeiger im Benutzermodus immer auf die unteren 2 GB des Adressraums verweisen. Was bedeutet, dass sie mit / 3GB schrecklich brechen können.

Normalerweise beschränkt Windows einen 32-Bit-Prozess jedoch auf einen Adressraum von 2 GB. Das heißt aber nicht, dass Sie damit rechnen sollten, 2 GB zuweisen zu können!

Der Adressraum ist bereits mit allen Arten von zugewiesenen Daten übersät. Es gibt den Stapel und alle geladenen Assemblys, statische Variablen und so weiter. Es gibt keine Garantie dafür, dass irgendwo 800 MB zusammenhängender nicht zugewiesener Speicher vorhanden sind.

Das Zuweisen von 2 400-MB-Blöcken wäre wahrscheinlich besser. Oder 4 200 MB große Stücke. Kleinere Zuordnungen sind in einem fragmentierten Speicherbereich viel einfacher zu finden.

Wenn Sie dies ohnehin auf einem 12-GB-Computer bereitstellen möchten, sollten Sie dies als 64-Bit-Anwendung ausführen, um alle Probleme zu lösen.

jalf
quelle
Das Aufteilen des Jobs in kleinere Teile scheint auch nicht zu helfen, mein Update oben zu sehen.
m3ntat
4

Der Wechsel von 32 auf 64 Bit hat bei mir funktioniert - einen Versuch wert, wenn Sie auf einem 64-Bit-PC arbeiten und dieser nicht portiert werden muss.

chris
quelle
1

32-Bit-Fenster haben eine Prozessspeicherbeschränkung von 2 GB. Die von anderen erwähnte / 3GB-Startoption macht diese 3 GB mit nur noch 1 GB für die Verwendung des Betriebssystemkerns. Realistisch gesehen ist ein 64-Bit-Betriebssystem erforderlich, wenn Sie problemlos mehr als 2 GB verwenden möchten. Dies überwindet auch das Problem, dass der für die Grafikkarte erforderliche Adressraum, obwohl Sie möglicherweise über 4 GB physischen RAM verfügen, einen beträchtlichen Speicherplatz unbrauchbar machen kann - normalerweise etwa 500 MB.

Redcalx
quelle
1

Könnten Sie versuchen, einen Iterator zu verwenden, anstatt ein massives Array zuzuweisen? Diese werden verzögert ausgeführt, dh Werte werden nur generiert, wenn sie in einer foreach-Anweisung angefordert werden. Auf diese Weise sollte Ihnen nicht der Speicher ausgehen:

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

Das oben Genannte generiert so viele Zufallszahlen, wie Sie möchten, generiert sie jedoch nur so, wie sie über eine foreach-Anweisung angefordert werden. Auf diese Weise wird Ihnen nicht der Speicher ausgehen.

Alternativ: Wenn Sie alle an einem Ort haben müssen, speichern Sie sie in einer Datei und nicht im Speicher.

Judah Gabriel Himango
quelle
Interessanter Ansatz, aber ich muss so viel wie möglich als Zufallszahlen-Repository in jeder Leerlaufzeit der restlichen Anwendung speichern, da diese App im 24-Stunden-Takt ausgeführt wird und mehrere geografische Regionen unterstützt (mehrere Monte-Carlo-Simulationsläufe), etwa 70% des Tages maximale CPU-Auslastung, die verbleibenden Zeiten im Laufe des Tages möchte ich Zufallszahlen in allen freien Speicherplatz puffern. Das Speichern auf der Festplatte ist zu langsam und besiegt alle Vorteile, die ich beim Puffern in diesen Zufallszahlen-Speichercache erzielen könnte.
m3ntat
0

Nun, ich habe ein ähnliches Problem mit großen Datenmengen und der Versuch, die Anwendung zu zwingen, so viele Daten zu verwenden, ist nicht wirklich die richtige Option. Der beste Tipp, den ich Ihnen geben kann, ist, Ihre Daten in kleinen Stücken zu verarbeiten, wenn dies möglich ist. Da es sich um so viele Daten handelt, wird das Problem früher oder später wieder auftreten. Außerdem können Sie nicht die Konfiguration jedes Computers kennen, auf dem Ihre Anwendung ausgeführt wird, sodass immer das Risiko besteht, dass die Ausnahme auf einem anderen PC auftritt.

Francis B.
quelle
Eigentlich kenne ich die Konfiguration der Maschine, diese läuft nur auf einem Server und ich kann diese für diese Spezifikationen schreiben. Dies ist für eine massive Monte-Carlo-Simulation und ich versuche zu optimieren, indem Zufallszahlen im Voraus gepuffert werden.
m3ntat
0

Ich hatte ein ähnliches Problem, es lag an einem StringBuilder.ToString ();

Ricardo Rix
quelle
Lass den Stringbuilder nicht so groß werden
Ricardo Rix
0

Konvertieren Sie Ihre Lösung in x64. Wenn Sie immer noch auf ein Problem stoßen, gewähren Sie allen, die eine Ausnahme wie die folgende auslösen, die maximale Länge:

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

Wenn Sie den Visual Studio-Hostingprozess nicht benötigen:

Deaktivieren Sie die Option: Projekt-> Eigenschaften-> Debug-> Aktivieren Sie den Visual Studio-Hosting-Prozess

Und dann bauen.

Wenn Sie immer noch vor dem Problem stehen:

Gehen Sie zu Projekt-> Eigenschaften-> Ereignisse erstellen-> Ereignisereignis nach dem Erstellen und fügen Sie Folgendes ein:

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

Erstellen Sie nun das Projekt.

Yasir Arafat
quelle
-2

Erhöhen Sie das Windows-Prozesslimit auf 3 GB. (über boot.ini oder Vista Boot Manager)

Leppie
quelle
Ja wirklich? Was ist der standardmäßige maximale Prozessspeicher? Und wie kann man das ändern? Wenn ich ein Spiel oder etwas auf meinem PC spiele, kann es problemlos 2+ GB von einer einzelnen EXE / einem einzelnen Prozess verbrauchen. Ich denke nicht, dass dies hier das Problem ist.
m3ntat
/ 3 GB ist dafür übertrieben und kann zu großer Instabilität führen, da viele Treiber davon ausgehen, dass Userspace-Zeiger immer auf die unteren 2 GB verweisen.
Jalf
1
m3ntat: Nein, in 32-Bit-Windows ist ein einzelner Prozess auf 2 GB beschränkt. Die verbleibenden 2 GB des Adressraums werden vom Kernel verwendet.
Jalf