To jest mój kod:
int size = 100000000;
double sizeInMegabytes = (size * 8.0) / 1024.0 / 1024.0; //762 mb
double[] randomNumbers = new double[size];
Wyjątek: został zgłoszony wyjątek typu „System.OutOfMemoryException”.
Mam 4 GB pamięci na tym komputerze. 2,5 GB jest wolne, kiedy uruchamiam to działanie, jest wystarczająco dużo miejsca na komputerze, aby obsłużyć 762 MB z 100000000 liczb losowych. Muszę zapisać jak najwięcej liczb losowych, biorąc pod uwagę dostępną pamięć. Kiedy przejdę do produkcji, na pudełku będzie 12 GB i chcę to wykorzystać.
Czy środowisko CLR ogranicza mnie do domyślnej maksymalnej pamięci na początek? i jak poprosić o więcej?
Aktualizacja
Pomyślałem, że podzielenie tego na mniejsze fragmenty i stopniowe zwiększanie wymagań dotyczących pamięci pomogłoby, gdyby problem wynikał z fragmentacji pamięci , ale nie mogę przekroczyć całkowitego rozmiaru ArrayList wynoszącego 256 MB, niezależnie od tego, co robię, dostosowując rozmiar bloku .
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()));
}
}
Z mojej głównej metody:
int blockSize = 1000000;
while (true) {
try
{
AddNDRandomNumbers(blockSize);
}
catch (System.OutOfMemoryException ex)
{
break;
}
}
double arrayTotalSizeInMegabytes = (ndRandomNumbers.Count * 8.0) / 1024.0 / 1024.0;
źródło
Odpowiedzi:
Możesz przeczytać to: „ Brak pamięci” nie odnosi się do pamięci fizycznej ”autorstwa Erica Lipperta.
Krótko mówiąc i bardzo uproszczone, „Brak pamięci” nie oznacza tak naprawdę, że ilość dostępnej pamięci jest zbyt mała. Najczęstszym powodem jest to, że w bieżącej przestrzeni adresowej nie ma ciągłej części pamięci, która byłaby wystarczająco duża, aby obsłużyć żądaną alokację. Jeśli masz 100 bloków, każdy o wielkości 4 MB, to nie pomoże ci, gdy potrzebujesz jednego bloku 5 MB.
Kluczowe punkty:
źródło
Sprawdź, czy tworzysz proces 64-bitowy, a nie 32-bitowy, który jest domyślnym trybem kompilacji programu Visual Studio. Aby to zrobić, kliknij prawym przyciskiem myszy swój projekt, Właściwości -> Kompiluj -> docelowa platforma: x64. Jak każdy proces 32-bitowy, aplikacje Visual Studio skompilowane w wersji 32-bitowej mają limit pamięci wirtualnej wynoszący 2 GB.
Procesy 64-bitowe nie mają tego ograniczenia, ponieważ używają wskaźników 64-bitowych, więc ich teoretyczna maksymalna przestrzeń adresowa (rozmiar ich pamięci wirtualnej) wynosi 16 eksabajtów (2 ^ 64). W rzeczywistości Windows x64 ogranicza pamięć wirtualną procesów do 8 TB. Rozwiązaniem problemu z limitem pamięci jest wtedy kompilacja w wersji 64-bitowej.
Jednak rozmiar obiektu w programie Visual Studio jest nadal domyślnie ograniczony do 2 GB. Będziesz mógł utworzyć kilka tablic, których łączny rozmiar będzie większy niż 2 GB, ale domyślnie nie możesz tworzyć tablic większych niż 2 GB. Mamy nadzieję, że jeśli nadal chcesz tworzyć tablice większe niż 2 GB, możesz to zrobić, dodając następujący kod do pliku app.config:
<configuration> <runtime> <gcAllowVeryLargeObjects enabled="true" /> </runtime> </configuration>
źródło
Nie masz ciągłego bloku pamięci, aby przydzielić 762 MB, twoja pamięć jest pofragmentowana, a alokator nie może znaleźć wystarczająco dużego otworu, aby przydzielić potrzebną pamięć.
źródło
Jak zapewne się domyślasz, problem polega na tym, że próbujesz przydzielić jeden duży, ciągły blok pamięci, który nie działa z powodu fragmentacji pamięci. Gdybym musiał zrobić to, co robisz, zrobiłbym następujące:
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]; }
Następnie, aby uzyskać określony indeks, którego użyjesz
randomNumbers[i / sizeB][i % sizeB]
.Inną opcją, jeśli zawsze uzyskujesz dostęp do wartości w kolejności, może być użycie przeciążonego konstruktora do określenia ziarna. W ten sposób otrzymujesz półlosową liczbę (taką jak
DateTime.Now.Ticks
), przechowuj ją w zmiennej, a następnie kiedykolwiek zaczniesz przeglądać listę, utworzysz nową instancję Random, używając oryginalnego ziarna: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); }
Należy zauważyć, że chociaż blog, do którego link znajduje się w odpowiedzi Fredrika Mörka, wskazuje, że problem jest zwykle spowodowany brakiem przestrzeni adresowej , nie zawiera on wielu innych problemów, takich jak ograniczenie rozmiaru obiektu CLR do 2 GB (wspomniane w komentarzu z ShuggyCoUk na tym samym blogu), omawia fragmentację pamięci i nie wspomina o wpływie rozmiaru pliku strony (i o tym, jak można go rozwiązać za pomocą
CreateFileMapping
funkcji ).Ograniczenie 2 GB oznacza, że
randomNumbers
musi być mniej niż 2 GB. Ponieważ tablice są klasami i mają same narzuty, oznacza to, że tablicadouble
będzie musiała być mniejsza niż 2 ^ 31. Nie jestem pewien, o ile mniejsza musiałaby być długość 2 ^ 31, ale narzut macierzy .NET? wskazuje 12-16 bajtów.Fragmentacja pamięci jest bardzo podobna do fragmentacji dysku twardego. Możesz mieć 2 GB przestrzeni adresowej, ale podczas tworzenia i niszczenia obiektów będą luki między wartościami. Jeśli te luki są zbyt małe dla dużego obiektu i nie można zażądać dodatkowej przestrzeni, otrzymasz plik
System.OutOfMemoryException
. Na przykład, jeśli utworzysz 2 miliony obiektów 1024-bajtowych, używasz 1,9 GB. Jeśli usuniesz każdy obiekt, którego adres nie jest wielokrotnością 3, użyjesz 0,6 GB pamięci, ale zostanie ona rozłożona w przestrzeni adresowej z 2024-bajtowymi otwartymi blokami pomiędzy nimi. Jeśli potrzebujesz stworzyć obiekt o wielkości .2GB, nie będziesz w stanie tego zrobić, ponieważ nie ma bloku wystarczająco dużego, aby go zmieścić i nie można uzyskać dodatkowej przestrzeni (zakładając środowisko 32-bitowe). Możliwe rozwiązania tego problemu to na przykład użycie mniejszych obiektów, zmniejszenie ilości danych przechowywanych w pamięci lub użycie algorytmu zarządzania pamięcią w celu ograniczenia / zapobieżenia fragmentacji pamięci. Należy zauważyć, że jeśli nie tworzysz dużego programu, który używa dużej ilości pamięci, nie będzie to problemem. Również,Ponieważ większość programów żąda pamięci roboczej z systemu operacyjnego i nie żąda mapowania plików, będą one ograniczone przez pamięć RAM systemu i rozmiar pliku stronicowania. Jak zauważono w komentarzu Néstora Sáncheza (Néstor Sánchez) na blogu, z kodem zarządzanym, takim jak C #, utkniesz w ograniczeniu pamięci RAM / pliku stronicowania i przestrzeni adresowej systemu operacyjnego.
To było o wiele dłużej niż oczekiwano. Mam nadzieję, że to komuś pomoże. Opublikowałem to, ponieważ natknąłem się na
System.OutOfMemoryException
uruchamianie programu x64 w systemie z 24 GB pamięci RAM, mimo że moja tablica zawierała tylko 2 GB.źródło
Odradzałbym opcję uruchamiania systemu Windows / 3GB. Oprócz wszystkiego innego (przesadą jest robienie tego dla jednej źle działającej aplikacji, a prawdopodobnie i tak nie rozwiąże to twojego problemu), może to spowodować dużą niestabilność.
Wiele sterowników systemu Windows nie jest testowanych z tą opcją, więc sporo z nich zakłada, że wskaźniki trybu użytkownika zawsze wskazują niższe 2 GB przestrzeni adresowej. Co oznacza, że mogą strasznie się zepsuć z / 3GB.
Jednak system Windows zwykle ogranicza proces 32-bitowy do 2 GB przestrzeni adresowej. Ale to nie znaczy, że powinieneś spodziewać się możliwości przydzielenia 2 GB!
Przestrzeń adresowa jest już zaśmiecona różnymi rodzajami przydzielonych danych. Jest stos i wszystkie załadowane zestawy, zmienne statyczne i tak dalej. Nie ma gwarancji, że gdziekolwiek będzie dostępne 800 MB ciągłej, nieprzydzielonej pamięci.
Przydzielenie 2 400 MB fragmentów prawdopodobnie wypadłoby lepiej. Lub 4 kawałki po 200 MB. Mniejsze alokacje są znacznie łatwiejsze do znalezienia w pofragmentowanej przestrzeni pamięci.
W każdym razie, jeśli i tak zamierzasz wdrożyć to na maszynie 12 GB, będziesz chciał uruchomić to jako aplikację 64-bitową, która powinna rozwiązać wszystkie problemy.
źródło
Zmiana z 32 na 64 bit działała dla mnie - warto spróbować, jeśli używasz 64-bitowego komputera i nie ma potrzeby przenoszenia.
źródło
Jeśli potrzebujesz tak dużych struktur, być może możesz użyć plików mapowanych w pamięci. Ten artykuł może okazać się pomocny: http://www.codeproject.com/KB/recipes/MemoryMappedGenericArray.aspx
LP, Dejan
źródło
32-bitowe okna mają limit pamięci procesowej 2 GB. Opcja rozruchu / 3 GB, o której wspominali inni, spowoduje, że będzie to 3 GB, a pozostanie tylko 1 GB na jądro systemu operacyjnego. Realistycznie, jeśli chcesz bezproblemowo korzystać z więcej niż 2 GB, wymagany jest 64-bitowy system operacyjny. To również rozwiązuje problem polegający na tym, że chociaż możesz mieć 4 GB fizycznej pamięci RAM, przestrzeń adresowa wymagana dla karty graficznej może sprawić, że spora część tej pamięci stanie się bezużyteczna - zwykle około 500 MB.
źródło
Zamiast alokować ogromną tablicę, czy możesz spróbować użyć iteratora? Są one wykonywane z opóźnieniem, co oznacza, że wartości są generowane tylko wtedy, gdy są żądane w instrukcji foreach; w ten sposób nie powinno zabraknąć pamięci:
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); }
Powyższe wygeneruje tyle liczb losowych, ile chcesz, ale generuje je tylko wtedy, gdy są proszone za pomocą instrukcji foreach. W ten sposób nie zabraknie Ci pamięci.
Alternatywnie, jeśli musisz mieć je wszystkie w jednym miejscu, przechowuj je w pliku, a nie w pamięci.
źródło
Cóż, mam podobny problem z dużym zestawem danych i próba zmuszenia aplikacji do wykorzystania tak dużej ilości danych nie jest tak naprawdę właściwą opcją. Najlepszą wskazówką, jaką mogę ci dać, jest przetwarzanie danych w małych porcjach, jeśli jest to możliwe. Ponieważ przy tak dużej ilości danych problem prędzej czy później powróci. Ponadto nie możesz znać konfiguracji każdego komputera, na którym będzie działać Twoja aplikacja, więc zawsze istnieje ryzyko, że wyjątek zdarzy się na innym komputerze.
źródło
Miałem podobny problem, był to skutek StringBuilder.ToString ();
źródło
Przekonwertuj swoje rozwiązanie na x64. Jeśli nadal masz problem, nadaj maksymalną długość wszystkim, co powoduje wyjątek, jak poniżej:
var jsSerializer = new JavaScriptSerializer(); jsSerializer.MaxJsonLength = Int32.MaxValue;
źródło
Jeśli nie potrzebujesz procesu hostingu programu Visual Studio:
Odznacz opcję: Projekt-> Właściwości-> Debuguj-> Włącz proces hostingu programu Visual Studio
A potem buduj.
Jeśli nadal masz problem:
Przejdź do Project-> Properties-> Build Events-> Po-Build Event, wiersz poleceń i wklej:
call "$(DevEnvDir)..\..\vc\vcvarsall.bat" x86 "$(DevEnvDir)..\..\vc\bin\EditBin.exe" "$(TargetPath)" /LARGEADDRESSAWARE
Teraz skompiluj projekt.
źródło
Zwiększ limit procesów systemu Windows do 3 GB. (przez boot.ini lub menedżera rozruchu Vista)
źródło