Pytanie Wywołano "System.OutOfMemoryException", gdy wciąż jest dużo wolnej pamięci


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: Wyjątek typu "System.OutOfMemoryException 'został wyrzucony.

Mam 4 GB pamięci na tym komputerze 2,5 GB jest bezpłatne kiedy zaczynam ten bieg, jest wystarczająco dużo miejsca na PC, aby obsłużyć 762 MB z 100000000 liczb losowych. Muszę przechowywać jak najwięcej losowych liczb, biorąc pod uwagę dostępną pamięć. Kiedy pójdę do produkcji, na pudełku pojawi się 12 GB i chcę z niego skorzystać.

Czy CLR ogranicza mnie do domyślnej maksymalnej pamięci na początek? i jak mogę prosić o więcej?

Aktualizacja

Pomyślałem, że podzielenie tego na mniejsze części i przyrostowe dodawanie do moich wymagań pamięciowych pomogłoby, gdyby problem był spowodowany fragmentacja pamięci, ale tak nie jest Nie mogę przekroczyć całkowitej wielkości tablicy ArrayList wynoszącej 256 MB, niezależnie od tego, co robię, modyfikując 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()));                
  }
}

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;

76
2017-07-20 13:50


pochodzenie


Polecam ponowne przeanalizowanie aplikacji, abyś nie musiał zużywać tak dużo pamięci. Co robisz, że potrzebujesz stu milionów liczb w pamięci jednocześnie? - Eric Lippert
nie wyłączyłeś swojego pliku stronicowania lub czegoś takiego głupiego, prawda? - jalf
@EricLippert, używam tego podczas pracy nad problemem P vs. NP (claymath.org/millenium-problems/p-vs-np-problem). Czy masz sugestię dotyczącą zmniejszenia użycia pamięci roboczej? (na przykład szeregowanie i przechowywanie porcji danych na dysku twardym, przy użyciu typu danych C ++ itp.) - devinbost
@ Bosit to jest strona z pytaniami i odpowiedziami. Jeśli masz konkretne pytanie techniczne dotyczące faktycznego kodu, a następnie opublikuj go jako pytanie. - Eric Lippert
@bystIT link dla problemu P vs. NP w twoim komentarzu jest już nieważny. - RBT


Odpowiedzi:


Możesz przeczytać to: ""Brak pamięci" nie odnosi się do pamięci fizycznej"Eric Lippert.

Krótko mówiąc i bardzo uproszczony "brak pamięci" nie oznacza, że ​​ilość dostępnej pamięci jest zbyt mała. Najczęstszym powodem jest to, że w obecnej przestrzeni adresowej nie ma ciągłej części pamięci, która jest wystarczająco duża, aby obsługiwać pożądaną alokację. Jeśli masz 100 bloków, każde 4 MB duże, to nie pomoże, gdy potrzebujesz jednego bloku 5 MB.

Kluczowe punkty: 

  • przechowywanie danych, które nazywamy "pamięcią procesową", jest moim zdaniem najlepiej wizualizowane jako ogromny plik na dysku.
  • Pamięć RAM może być postrzegana jedynie jako optymalizacja wydajności
  • Całkowita ilość pamięci wirtualnej, którą zużywa twój program, nie ma większego związku z jej wydajnością
  • "brak pamięci RAM" rzadko powoduje błąd "braku pamięci". Zamiast błędu powoduje to złą wydajność, ponieważ całkowity koszt przechowywania danych na dysku staje się nagle istotny.

116
2017-07-20 13:58





Nie masz ciągłego bloku pamięci, aby przydzielić 762 MB, twoja pamięć jest pofragmentowana i alokator nie może znaleźć wystarczająco dużej dziury do przydzielenia potrzebnej pamięci.

  1. Możesz spróbować pracować z / 3GB (jak sugerowali inni)
  2. Lub przełącz się na 64-bitowy system operacyjny.
  3. Lub zmodyfikuj algorytm, aby nie potrzebował dużej części pamięci. może przydzielić kilka mniejszych (względnie) porcji pamięci.

23
2017-07-20 13:59





Sprawdź, czy budujesz proces 64-bitowy, a nie 32-bitowy, który jest domyślnym trybem kompilacji programu Visual Studio. Aby to zrobić, kliknij prawym przyciskiem myszy na swój projekt, Właściwości -> Utwórz -> Cel platformy: 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 (wielkość ich wirtualnej pamięci) 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 następnie kompilacja w wersji 64-bitowej.

Jednak rozmiar obiektu w Visual Studio jest domyślnie ograniczony do 2 GB. Będziesz mógł stworzyć kilka tablic, których łączny rozmiar będzie większy niż 2 GB, ale nie możesz domyślnie tworzyć tablic większych niż 2 GB. Mam 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>

18
2018-06-26 13:56





Jak zapewne domyślacie się, 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 potrzebował zrobić to, co robisz, zrobiłbym:

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ć konkretny 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ążony konstruktor aby określić nasiona. W ten sposób otrzymasz liczbę losową (np DateTime.Now.Ticks) przechowuj go w zmiennej, a kiedy kiedykolwiek zaczniesz przeglądać listę, utworzysz nowe Losowe wystąpienie używając oryginalnego 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);
}

Ważne jest, aby pamiętać, że chociaż blog powiązany z odpowiedzią Fredrika Mörka wskazuje, że problem jest zazwyczaj spowodowany brakiem przestrzeń adresowa nie zawiera wielu innych problemów, takich jak ograniczenie rozmiaru obiektu CLR o rozmiarze 2 GB (wspomniane w komentarzu ShuggyCoUk na tym samym blogu), pomija fragmentację pamięci i nie wspomina o wpływie rozmiaru pliku strony (oraz o tym, jak może być skierowane za pomocą CreateFileMapping funkcjonować).

Ograniczenie 2 GB oznacza, że randomNumbers  musi być mniejsza niż 2 GB. Ponieważ tablice są klasami i mają trochę narzutów na siebie, oznacza to tablicę double będzie musiał być mniejszy niż 2 ^ 31. Nie jestem pewien, o ile mniejszy niż 2 ^ 31 musiałby być Długości, ale Obciążenie tablicy .NET? wskazuje 12-16 bajtów.

Fragmentacja pamięci jest bardzo podobna do fragmentacji HDD. Możesz mieć 2 GB przestrzeni adresowej, ale podczas tworzenia i niszczenia obiektów będą występować przerwy między wartościami. Jeśli te luki są zbyt małe dla dużego obiektu, a dodatkowe miejsce nie może być wymagane, otrzymasz System.OutOfMemoryException. Na przykład, jeśli utworzysz 2 miliony obiektów o rozmiarze 1024 bajty, używasz 1,9 GB. Jeśli usuniesz każdy obiekt, w którym adres nie jest wielokrotnością liczby 3, użyjesz pamięci o pojemności 6 GB, ale zostanie ona rozłożona na przestrzeni adresowej z 2024 bajtowymi blokami pośrednimi pomiędzy nimi. Jeśli potrzebujesz utworzyć obiekt, który był .2GB, nie byłbyś w stanie tego zrobić, ponieważ nie ma wystarczająco dużego bloku, aby go zmieścić i nie można uzyskać dodatkowej przestrzeni (zakładając środowisko 32-bitowe). Możliwe rozwiązania tego problemu to np. Używanie mniejszych obiektów, zmniejszanie ilości danych przechowywanych w pamięci lub stosowanie algorytmu zarządzania pamięcią w celu ograniczenia / zapobiegania fragmentacji pamięci. Należy zauważyć, że dopóki nie opracujesz dużego programu, który używa dużej ilości pamięci, nie będzie to problemem. Ponadto ten problem może występować w systemach 64-bitowych, ponieważ system Windows jest ograniczony głównie przez rozmiar pliku stronicowania i ilość pamięci RAM w systemie.

Ponieważ większość programów żąda pamięci roboczej z systemu operacyjnego i nie żąda mapowania plików, będą one ograniczone przez systemową pamięć RAM i rozmiar pliku stronicowania. Jak zauważono w komentarzu Néstora Sáncheza (Néstor Sánchez) na blogu, przy zarządzanym kodzie takim jak C # utknąłeś na ograniczeniu RAM / strony i przestrzeni adresowej systemu operacyjnego.


To było znacznie dłużej, niż się spodziewano. Mam nadzieję, że to komuś pomaga. Wysłałem to, ponieważ wpadłem na System.OutOfMemoryException uruchomienie programu x64 w systemie z 24 GB pamięci RAM, mimo że moja tablica zawierała tylko 2 GB danych.


7
2017-12-24 23:15





Odradzam opcję uruchamiania systemu windows / 3GB. Poza wszystkim innym (to przesada, żeby to zrobić jeden źle zachowana aplikacja, i prawdopodobnie i tak nie rozwiąże ona twojego problemu), może spowodować wiele niestabilności.

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ższą 2 GB przestrzeni adresowej. Co oznacza, że ​​mogą przerwać się z / 3GB.

Jednak system Windows zwykle ogranicza proces 32-bitowy do przestrzeni adresowej o pojemności 2 GB. Ale to nie znaczy, że powinieneś oczekiwać, że będziesz w stanie przydzielić 2 GB!

Przestrzeń adresowa jest już zaśmiecona wszelkiego rodzaju przydzielonymi danymi. Jest stos i wszystkie załadowane zestawy, zmienne statyczne i tak dalej. Nie ma gwarancji, że w dowolnym miejscu będzie 800 MB sąsiadującej nieprzydzielonej pamięci.

Przydzielanie 2 400 MB porcji prawdopodobnie byłoby lepsze. Lub 4 kawałki 200 MB. Mniejsze przydziały są o wiele łatwiejsze do znalezienia w pofragmentowanej przestrzeni pamięci.

W każdym razie, jeśli zamierzasz wdrożyć to na komputerze o pojemności 12 GB, będziesz chciał uruchomić to jako aplikację 64-bitową, która powinna rozwiązać wszystkie problemy.


5
2017-07-20 14:05



Podzielenie zadania na mniejsze porcje nie wydaje się pomóc, aby zobaczyć moją aktualizację powyżej. - m3ntat


Zmienia się od 32 do 64 bitów - warto spróbować, jeśli jesteś na 64-bitowym komputerze i nie trzeba go portować.


3
2017-08-31 21:04





Jeśli potrzebujesz tak dużych struktur, być może możesz wykorzystać pliki mapowane na pamięć. Ten artykuł może okazać się pomocny: http://www.codeproject.com/KB/recipes/MemoryMappedGenericArray.aspx

LP, Dejan


2
2017-07-20 14:09





32-bitowe okna mają limit pamięci procesowej 2 GB. Opcja bootowania / 3GB, o której wspomnieli inni, sprawi, że ten 3 GB pozostanie tylko 1 GB na jądro systemu operacyjnego. Realistycznie, jeśli chcesz użyć więcej niż 2 GB bez kłopotów, 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 znaczny uchwyt tej pamięci będzie nieużyteczny - zwykle około 500 MB.


1
2017-07-20 14:03





Zamiast alokować ogromną tablicę, możesz spróbować użyć iteratora? Są wykonywane z opóźnieniem, co oznacza, że ​​wartości są generowane tylko tak, jak są żądane w instrukcji foreach; nie powinno zabraknąć pamięci w ten sposób:

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, gdy są żądane za pośrednictwem instrukcji foreach. W ten sposób nie zabraknie pamięci.

Alternatywnie, jeśli musisz mieć je wszystkie w jednym miejscu, przechowuj je w pliku, a nie w pamięci.


1
2017-07-20 17:05



Ciekawe podejście, ale muszę przechowywać jak najwięcej jako repozytorium liczb losowych w dowolnym czasie bezczynności reszty aplikacji, ponieważ ta aplikacja działa na 24-godzinnym zegarze obsługującym wiele regionów geograficznych (wiele przebiegów symulacji monte carlo), około 70% dziennego maksymalnego obciążenia procesora, pozostałe czasy w ciągu dnia chcę oddzielić losowe liczby w całej wolnej pamięci. Przechowywanie na dysku jest zbyt powolne i pokonuje wszelkie zyski, które mógłbym buforować w tej pamięci podręcznej pamięci liczb losowych. - m3ntat


Cóż, mam podobny problem z dużym zbiorem danych i próbuję zmusić aplikację do użycia tak dużej ilości danych, że nie jest to właściwa opcja. Najlepszą wskazówką, którą mogę ci podać, jest przetwarzanie twoich danych w małych porcjach, o ile jest to możliwe. Ponieważ zajmuje się tak dużą ilością danych, problem wróci wcześniej lub później. Poza tym nie możesz znać konfiguracji każdego komputera, który uruchomi aplikację, więc zawsze istnieje ryzyko, że wyjątek wystąpi na innym komputerze.


0
2017-07-20 14:05



Właściwie znam konfigurację maszyny, działa ona tylko na jednym serwerze i mogę napisać to dla tych specyfikacji. To jest dla ogromnej symulacji Monte Carlo i staram się zoptymalizować, buforując losowe numery z góry. - m3ntat