wake-up-neo.net

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

Das ist mein Code:

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

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

Ich habe 4 GB Speicher auf dieser Maschine, 2,5 GB sind kostenlos Wenn ich mit dem Laufen beginne, ist auf dem PC eindeutig genügend Speicherplatz vorhanden, um die 762 MB von 100000000 Zufallszahlen zu handhaben. Ich muss so viele Zufallszahlen wie möglich speichern, wenn verfügbarer Speicher verfügbar ist. Wenn ich zur Produktion gehe, werden 12 GB auf der Box sein, und ich möchte davon Gebrauch machen.

Beschränkt mich die CLR auf einen voreingestellten maximalen Arbeitsspeicher, um damit zu beginnen? und wie fordere ich mehr an?

Update

Ich dachte, es wäre hilfreich, wenn ich dieses Problem in kleinere Blöcke zerlegte und meinen Speicherbedarf schrittweise steigerte. Dies ist jedoch auf Speicherfragmentierung zurückzuführen, aber es kommt nicht ich komme an einer ArrayList-Gesamtgröße von 256 MB nicht vorbei was ich mache 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()));                
  }
}

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;
81
m3ntat

Möglicherweise möchten Sie Folgendes lesen: " " Out Of Memory "bezieht sich nicht auf den physischen Speicher " von Eric Lippert.

Kurz und sehr vereinfacht bedeutet "Nicht genügend Arbeitsspeicher" nicht, dass der verfügbare Arbeitsspeicher zu klein ist. Der häufigste Grund ist, dass sich innerhalb des aktuellen Adressraums kein zusammenhängender Speicherbereich befindet, der groß genug ist, um die gewünschte Zuordnung zu erfüllen. Wenn Sie über 100 Blöcke mit jeweils 4 MB verfügen, wird Ihnen dies nicht helfen, wenn Sie einen 5-MB-Block benötigen.

Wichtige Punkte: 

  • der Datenspeicher, den wir als "Prozessspeicher" bezeichnen, wird meiner Meinung nach am besten als massive Datei auf der Festplatte visualisiert.
  • RAM kann nur als eine Leistungsoptimierung gesehen werden
  • Die Gesamtmenge an virtuellem Speicher, die Ihr Programm belegt, ist für die Leistung wirklich nicht von großer Bedeutung
  • "Nicht genügend RAM" führt selten zu einem Fehler "Nicht genügend Arbeitsspeicher". Anstelle eines Fehlers führt dies zu einer schlechten Leistung, da die vollen Kosten der Tatsache, dass der Speicher tatsächlich auf der Festplatte liegt, plötzlich relevant werden.
125
Fredrik Mörk

Sie haben keinen fortlaufenden Speicherblock, um 762 MB zuzuweisen, Ihr Speicher ist fragmentiert und der Zuweiser kann kein großes Loch finden, um den benötigten Speicher zuzuweisen. 

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

Stellen Sie sicher, dass Sie einen 64-Bit-Prozess und nicht einen 32-Bit-Prozess erstellen. Dies ist der Standardkompilierungsmodus von Visual Studio. Klicken Sie dazu mit der rechten Maustaste auf Ihr Projekt, Eigenschaften -> Erstellen -> Plattformziel: x64. Wie jeder 32-Bit-Prozess haben Visual Studio-Anwendungen, die in 32-Bit kompiliert wurden, eine Begrenzung des virtuellen Speichers von 2 GB.

64-Bit-Prozesse unterliegen dieser Einschränkung nicht, da sie 64-Bit-Zeiger verwenden. Ihr theoretischer maximaler Adressraum (die Größe des virtuellen Speichers) beträgt 16 Exabyte (2 ^ 64). In der Realität beschränkt Windows x64 den virtuellen Speicher von Prozessen auf 8 TB. Die Lösung für das Speicherbegrenzungsproblem besteht 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 erstellen möchten, die größer als 2 GB sind, können Sie dies tun, indem Sie der app.config-Datei den folgenden Code hinzufügen:

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

Wie Sie wahrscheinlich herausgefunden haben, besteht das Problem darin, dass Sie versuchen, einen großen zusammenhängenden Speicherblock zuzuweisen, der aufgrund der Fragmentierung des Speichers nicht funktioniert. Wenn ich das 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];
}

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

Eine weitere Option, wenn Sie immer auf die Werte zugreifen, in der Reihenfolge, könnte die Verwendung von Überladener Konstruktor zur Angabe des Seeds verwendet werden. Auf diese Weise würden Sie eine halbe Zufallszahl (wie DateTime.Now.Ticks ) in einer Variablen speichern. Wenn Sie dann die Liste durchgehen, erstellen Sie eine neue Zufallsinstanz, die den ursprünglichen Samen verwendet:

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

Beachten Sie, dass das in der Antwort von Fredrik Mörk verlinkte Blog darauf hinweist, dass das Problem in der Regel auf einen Mangel an address space zurückzuführen ist, eine Reihe anderer Probleme wie die Beschränkung auf 2 GB CLR-Objektgröße (erwähnt) nicht aufführt In einem Kommentar von ShuggyCoUk in demselben Blog) wird die Fragmentierung des Speichers auf den Punkt gebracht und die Auswirkung der Größe der Seitendatei nicht erwähnt (und wie diese mit der Funktion CreateFileMapping [ ] angegangen werden kann).

Die Einschränkung von 2 GB bedeutet, dass randomNumbers weniger als 2 GB sein muss. Da Arrays Klassen sind und einen gewissen Overhead haben, bedeutet dies, dass ein Array von double kleiner als 2 ^ 31 sein muss. Ich bin mir 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 Fragmentierung des Speichers ist der Fragmentierung der Festplatte sehr ähnlich. Sie verfügen möglicherweise über einen Adressraum von 2 GB, aber beim Erstellen und Zerstören von Objekten gibt es Lücken zwischen den Werten. Wenn diese Lücken für Ihr großes Objekt zu klein sind und kein zusätzlicher Speicherplatz angefordert werden kann, erhalten Sie den System.OutOfMemoryException. Wenn Sie beispielsweise 2 Millionen Objekte mit 1024 Byte 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 Arbeitsspeicher, werden jedoch mit 2024 Byte offenen Blöcken auf den Adressraum verteilt. Wenn Sie ein Objekt erstellen müssen, das 0,2 GB groß ist, können Sie dies nicht tun, da es keinen Block gibt, der groß genug ist, um darin Platz zu finden, und zusätzlicher Speicherplatz nicht 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 Verringerung der im Speicher gespeicherten Daten oder die Verwendung eines Speicherverwaltungsalgorithmus, um die Fragmentierung des Speichers zu begrenzen oder zu verhindern. Es sollte beachtet werden, dass dies kein Problem darstellt, wenn Sie kein großes Programm entwickeln, das viel Speicher benötigt. Dieses Problem kann auch auf 64-Bit-Systemen auftreten, da Fenster hauptsächlich durch die Dateigröße der Seite und die Anzahl von RAM auf dem System begrenzt sind.

Da die meisten Programme Arbeitsspeicher vom Betriebssystem anfordern und keine Dateizuordnung anfordern, werden sie durch die RAM des Systems und die Dateigröße der Seite begrenzt. Wie in dem Kommentar von Néstor Sánchez (Néstor Sánchez) auf dem Blog erwähnt, bleiben Sie bei verwaltetem Code wie C # an der Beschränkung auf RAM-/Seitendateien und dem Adressraum des Betriebssystems hängen.


Das war viel länger als erwartet. Hoffentlich hilft es jemandem. Ich habe es gepostet, weil ich mit System.OutOfMemoryException ein x64-Programm auf einem System mit 24 GB RAM ausgeführt habe, obwohl mein Array nur 2 GB an Daten enthielt.

7
Trisped

Ich würde von der/3GB-Windows-Startoption abraten. Abgesehen von allem anderen (es wäre übertrieben, dies für one schlecht verhaltene Anwendungen zu tun, und es wird Ihr Problem wahrscheinlich sowieso nicht lösen), kann dies zu einer großen Instabilität führen.

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

Normalerweise beschränkt Windows einen 32-Bit-Prozess jedoch auf einen Adressraum von 2 GB Dies bedeutet jedoch nicht, dass Sie damit rechnen müssen, 2 GB zuzuordnen!

Der Adressraum ist bereits mit allen Arten von zugewiesenen Daten belegt. Es gibt den Stapel und alle geladenen Baugruppen, statische Variablen und so weiter. Es kann nicht garantiert werden, dass 800 MB zusammenhängender, nicht zugewiesener Speicher vorhanden sind.

Die Zuteilung von 2 400 MB-Blöcken würde wahrscheinlich besser abschneiden. Oder 4 200MB-Brocken. Kleinere Zuordnungen sind in einem fragmentierten Speicherplatz viel einfacher zu finden.

Wenn Sie dies dennoch auf einer 12-GB-Maschine bereitstellen möchten, sollten Sie dies als 64-Bit-Anwendung ausführen, die alle Probleme lösen sollte.

5
jalf

Der Wechsel von 32 auf 64 Bit hat für mich funktioniert - einen Versuch wert, wenn Sie sich auf einem 64-Bit-PC befinden und nicht portieren müssen.

3
chris

Wenn Sie solche großen Strukturen benötigen, könnten Sie möglicherweise Memory Mapped Files verwenden .. Dieser Artikel könnte hilfreich sein: http://www.codeproject.com/KB/recipes/MemoryMappedGenericArray.aspx

LP, Dejan

2
Dejan Stanič

Könnten Sie statt eines massiven Arrays einen Iterator verwenden? Diese werden mit Verzögerung ausgeführt, dh Werte werden nur generiert, wenn sie in einer foreach-Anweisung angefordert werden. Ihnen sollte der Speicher auf diese Weise nicht 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 Obige generiert beliebig viele Zufallszahlen, generiert diese aber nur so, wie sie von einer foreach-Anweisung verlangt werden. Sie werden auf diese Weise nicht genug Speicher haben.

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

32-Bit-Fenster haben eine Prozessspeichergrenze von 2 GB. Die Boot-Option "/ 3GB", die von anderen bereits erwähnt wurde, macht diese 3 GB aus, wobei nur noch 1 GB für den Betriebssystemkernel zur Verfügung stehen. Wenn Sie mehr als 2 GB problemlos verwenden möchten, ist ein 64-Bit-Betriebssystem erforderlich. Dies behebt auch das Problem, dass der für die Grafikkarte benötigte Adressraum zwar 4 GByte RAM haben kann, jedoch einen beträchtlichen Speicherplatz dieses Speichers unbrauchbar machen kann - normalerweise etwa 500 MB.

1
redcalx

Versuchen Sie setzen Sie den Wert der gcAllowVeryObjects - Eigenschaft auf True in der Anwendungsdatei configuration.

Caution Stellen Sie vor dem Aktivieren dieser Funktion sicher, dass Ihre Anwendung keinen unsicheren Code enthält, der davon ausgeht, dass alle Arrays kleiner als 2 GB sind. Unsicherer Code, der Arrays als Puffer verwendet, kann zum Beispiel für Pufferüberschreitungen anfällig sein, wenn davon ausgegangen wird, dass Arrays 2 GB nicht überschreiten.

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

Konvertieren Sie Ihre Lösung in x64. Wenn Sie immer noch mit einem Problem konfrontiert sind, gewähren Sie alles, was eine Ausnahme wie folgt auslöst:

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

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

0
Ricardo Rix

Wenn Sie den Visual Studio Hosting-Prozess nicht benötigen:

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

Und dann bauen.

Wenn Sie immer noch mit dem Problem konfrontiert sind:

Gehen Sie zu Projekt-> Eigenschaften-> Buildereignisse-> Post-Build-Ereignisbefehlszeile und fügen Sie Folgendes ein:

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

Erstellen Sie jetzt das Projekt.

0
Yasir Arafat

Nun, ich hatte 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 Abschnitten zu verarbeiten, wenn dies möglich ist. Da so viele Daten verarbeitet werden, wird das Problem früher oder später wieder auftreten. Außerdem können Sie die Konfiguration der einzelnen Computer, auf denen Ihre Anwendung ausgeführt wird, nicht kennen. Daher besteht immer das Risiko, dass die Ausnahme auf einem anderen PC auftritt.

0
Francis B.