Frage '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: Eine Fehlermeldung des Typs 'SystemOutOfMemoryException' wurde angezeigt.

Ich habe 4 GB Speicher auf dieser Maschine 2,5 GB ist kostenlos Wenn ich mit dem Laufen beginne, ist auf dem PC klar genug Platz, um die 762 MB 100000000 Zufallszahlen zu verarbeiten. Ich muss so viele zufällige Zahlen wie möglich speichern verfügbaren Speicher. Wenn ich in Produktion gehe, wird es 12 GB auf der Box geben und ich möchte es nutzen.

Beschränkt die CLR mich zu einem Standardmaximalspeicher, um damit zu beginnen? und wie fordere ich mehr?

Aktualisieren

Ich dachte, dass das Aufteilen in kleinere Stücke und inkrementelles Hinzufügen zu meinen Speicheranforderungen helfen würde, wenn das Problem darauf zurückzuführen ist Speicherfragmentierungaber das tut es nicht Ich kann eine ArrayList-Gesamtgröße von 256 MB nicht überschreiten, unabhängig davon, was ich an Blockgröße anpasse.

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;

76
2017-07-20 13:50


Ursprung


Antworten:


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

Kurz gesagt, und sehr vereinfacht bedeutet "nicht genug Arbeitsspeicher" nicht wirklich, dass die Menge an verfügbarem Speicher zu klein ist. Der häufigste Grund ist, dass innerhalb des aktuellen Adressraums kein zusammenhängender Teil des Speichers vorhanden ist, der groß genug ist, um die gewünschte Zuweisung zu bedienen. Wenn Sie 100 Blöcke haben, die jeweils 4 MB groß sind, wird Ihnen das nicht helfen, wenn Sie einen 5-MB-Block benötigen.

Schlüsselpunkte: 

  • der Datenspeicher, den wir "Prozessspeicher" nennen, ist meiner Meinung nach am besten als a visualisiert massive Datei auf der Festplatte.
  • RAM kann lediglich als Leistungsoptimierung angesehen werden
  • Der Gesamtbetrag des virtuellen Speichers, den Ihr Programm verbraucht, ist für seine Leistung nicht wirklich relevant
  • Wenn der RAM nicht mehr ausreicht, kommt es selten zu einem Speichermangel. Statt 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.

115
2017-07-20 13:58



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

  1. Sie können versuchen, mit / 3GB zu arbeiten (wie andere vorgeschlagen hatten)
  2. Oder wechseln Sie zu 64-Bit-Betriebssystem.
  3. Oder ändern Sie den Algorithmus so, dass er keinen großen Speicherblock benötigt. vielleicht ein paar kleinere (relativ) Speicherstücke zuordnen.

23
2017-07-20 13:59



Stellen Sie sicher, dass Sie einen 64-Bit-Prozess und keinen 32-Bit-Prozess erstellen. Dies ist der Standard-Kompilierungsmodus von Visual Studio. Klicken Sie dazu mit der rechten Maustaste auf Ihr Projekt, Eigenschaften -> Build -> Plattform Ziel: x64. Wie bei jedem 32-Bit-Prozess haben Visual Studio-Anwendungen, die in 32-Bit kompiliert wurden, einen virtuellen Speicher von 2 GB.

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

Die Größe des Objekts in Visual Studio ist jedoch standardmäßig auf 2 GB beschränkt. Sie können mehrere Arrays erstellen, deren kombinierte Größe größer als 2 GB ist, aber Sie können standardmäßig keine Arrays erstellen, die größer als 2 GB sind. Wenn Sie immer noch Arrays größer als 2 GB erstellen möchten, können Sie dies tun, indem Sie Ihrer app.config den folgenden Code hinzufügen:

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

18
2018-06-26 13:56



Wie Sie wahrscheinlich herausgefunden haben, ist das Problem, dass Sie versuchen, einen großen zusammenhängenden Speicherblock zuzuordnen, der aufgrund von Speicherfragmentierung nicht funktioniert. Wenn ich tun müsste, was du tust, 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, um einen bestimmten Index zu erhalten, würden Sie verwenden randomNumbers[i / sizeB][i % sizeB].

Eine andere Möglichkeit, wenn Sie immer auf die Werte in der Reihenfolge zugreifen, könnte sein, zu verwenden der überladene Konstruktor um den Samen zu spezifizieren. Auf diese Weise erhalten Sie eine halb zufällige Nummer (wie die DateTime.Now.Ticks) Speichern Sie es in einer Variablen. Wenn Sie dann mit der Liste beginnen, erstellen Sie eine neue Random-Instanz mit dem ursprünglichen Seed:

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 während der Blog in Fredrik Mörk Antwort darauf verweist, dass das Problem in der Regel aufgrund eines Mangels ist Adressraum es listet nicht eine Reihe von anderen Fragen, wie die Objektgröße Begrenzung 2GB CLR (in einem Kommentar von ShuggyCoUk auf dem gleichen Blog erwähnt), Speicherfragmentierung beschönigt und nicht die Auswirkungen der Größe der Auslagerungsdatei zu erwähnen (und wie kann es mit der Verwendung der adressiert werden CreateFileMapping Funktion).

Die 2GB-Begrenzung bedeutet das randomNumbers  muss weniger als 2 GB sein. Da Arrays Klassen sind und einige Gemeinkosten haben, bedeutet dies ein Array von double muss kleiner als 2 ^ 31 sein. Ich bin mir nicht sicher, wieviel kleiner als 2 ^ 31 die Länge wäre, aber Overhead eines .NET-Arrays? zeigt 12 - 16 Bytes an.

Die Speicherfragmentierung ist der HDD-Fragmentierung sehr ähnlich. Sie haben möglicherweise 2 GB Adressraum, aber beim Erstellen und Löschen von Objekten gibt es Lücken zwischen den Werten. Wenn diese Lücken für Ihr großes Objekt zu klein sind und zusätzlicher Platz nicht angefordert werden kann, erhalten Sie die 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, werden Sie 0,6 GB Speicher verwenden, aber es wird über den Adressraum mit 2024 Byte offenen Blöcken dazwischen verteilt. Wenn Sie ein Objekt erstellen müssen, das .2GB ist, können Sie dies nicht tun, da kein Block groß genug ist, um ihn einzupassen, und zusätzlicher Platz kann nicht erhalten werden (unter der Annahme einer 32-Bit-Umgebung). Mögliche Lösungen für dieses Problem sind Dinge wie die Verwendung kleinerer Objekte, die Reduzierung der Menge an Daten, die Sie im Speicher ablegen, oder die Verwendung eines Speicherverwaltungsalgorithmus zur Begrenzung / Verhinderung von Speicherfragmentierung. Es sollte beachtet werden, dass dies kein Problem darstellt, es sei denn, Sie entwickeln ein großes Programm, das eine große Menge an Speicher verwendet. Dieses Problem kann auch bei 64-Bit-Systemen auftreten, da Windows hauptsächlich durch die Größe der Auslagerungsdatei und die Größe des Arbeitsspeichers auf dem System begrenzt wird.

Da die meisten Programme Arbeitsspeicher vom Betriebssystem anfordern und keine Dateizuordnung anfordern, sind sie durch den Arbeitsspeicher des Systems und die Größe der Auslagerungsdatei begrenzt. Wie in dem Kommentar von Néstor Sánchez (Néstor Sánchez) auf dem Blog vermerkt, hängen Sie mit verwaltetem Code wie C # an der RAM- / Page-Dateibegrenzung und dem Adressraum des Betriebssystems fest.


Das war viel länger als erwartet. Hoffentlich hilft es jemandem. Ich habe es gepostet, weil ich in die System.OutOfMemoryException Ausführen eines x64-Programms auf einem System mit 24 GB RAM, obwohl mein Array nur 2 GB Speicher enthielt.


7
2017-12-24 23:15



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

Viele Windows-Treiber werden mit dieser Option nicht getestet, so dass einige davon annehmen, dass Benutzermoduszeiger immer auf die unteren 2 GB des Adressraums zeigen. Was bedeutet, dass sie schrecklich mit / 3GB brechen können.

In der Regel beschränkt Windows jedoch einen 32-Bit-Prozess auf einen 2-GB-Adressraum. Aber das bedeutet nicht, dass Sie erwarten sollten, 2GB 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, dass es irgendwo 800MB zusammenhängender nicht zugewiesener Speicher gibt.

Die Zuweisung von 2 400MB Brocken würde wahrscheinlich besser laufen. Oder 4 200MB Chunks. Kleinere Zuordnungen sind in einem fragmentierten Speicherplatz viel einfacher zu finden.

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


5
2017-07-20 14:05



Der Wechsel von 32 auf 64 Bit funktionierte für mich - einen Versuch wert, wenn Sie auf einem 64-Bit-PC sind und es nicht portieren muss.


3
2017-08-31 21:04



Wenn Sie solch große Strukturen benötigen, könnten Sie Memory Mapped Files verwenden. Dieser Artikel könnte sich als hilfreich erweisen: http://www.codeproject.com/KB/recipes/MemoryMappedGenericArray.aspx

LP, Dejan


2
2017-07-20 14:09



32-Bit-Fenster haben einen Prozessspeicher von 2 GB. Die / 3GB-Boot-Option, die andere erwähnt haben, wird diese 3 GB mit nur 1 GB für OS Kernel-Nutzung machen. Wenn Sie mehr als 2 GB ohne Probleme verwenden möchten, ist ein 64-Bit-Betriebssystem erforderlich. Dies überwindet auch das Problem, dass, obwohl Sie 4 GB physischen RAM haben können, der Adressraum, der für die Videokarte erforderlich ist, ein beträchtliches Chuck dieses Speichers unbrauchbar machen kann - normalerweise ungefähr 500 MB.


1
2017-07-20 14:03