Hala bol miktarda boş bellek varken 'System.OutOfMemoryException' atıldı


94

Bu benim kodum:

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

İstisna: 'System.OutOfMemoryException' türü özel durum oluşturuldu.

Bu makinede 4GB belleğim var 2.5GB bu çalışmaya başladığımda boş, PC'de 762mb 100000000 rasgele sayıları işlemek için yeterince yer var. Kullanılabilir hafıza göz önüne alındığında, mümkün olduğunca çok sayıda rastgele sayı kaydetmem gerekiyor. Üretime gittiğimde kutuda 12GB olacak ve bundan yararlanmak istiyorum.

CLR beni başlangıçta varsayılan bir maksimum bellekle sınırlıyor mu? ve nasıl daha fazlasını talep edebilirim?

Güncelleme

Bunu daha küçük parçalara bölmeyi ve bellek gereksinimlerime aşamalı olarak eklemenin, sorun bellek parçalanmasından kaynaklanıyorsa yardımcı olacağını düşündüm , ancak blockSize'ı ne yaparsam yapayım 256 MB'lik toplam ArrayList boyutunu geçemiyorum .

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

Ana yöntemimden:

int blockSize = 1000000;

while (true) {
  try
  {
    AddNDRandomNumbers(blockSize);                    
  }
  catch (System.OutOfMemoryException ex)
  {
    break;
  }
}            
double arrayTotalSizeInMegabytes = (ndRandomNumbers.Count * 8.0) / 1024.0 / 1024.0;

6
Çok fazla bellek kullanmak zorunda kalmamanız için uygulamanızı yeniden düzenlemenizi tavsiye ederim. Bir kerede hafızada yüz milyon sayıya ihtiyaç duyduğunuzda ne yapıyorsunuz?
Eric Lippert

2
sayfa dosyanızı veya bunun gibi aptalca bir şeyi devre dışı bırakmadınız, değil mi?
jalf

@EricLippert, P'ye karşı NP problemi ( claymath.org/millenium-problems/p-vs-np-problem ) üzerinde çalışırken bununla karşılaşıyorum . Çalışan bellek kullanımını azaltmak için bir öneriniz var mı? (örn. C ++ veri türünü kullanarak veri yığınlarını sabit diskte seri hale getirmek ve depolamak, vb.)
devinbost

@bosit bu bir soru cevap sitesidir. Gerçek kodla ilgili belirli bir teknik sorunuz varsa, bunu bir soru olarak gönderin.
Eric Lippert

@bostIT Yorumunuzdaki P - NP problemi bağlantısı artık geçerli değil.
RBT

Yanıtlar:


144

Bunu okumak isteyebilirsiniz: Eric Lippert'ten "" Bellek Yetersiz " Fiziksel Belleğe Başvurmuyor".

Kısacası ve çok basitleştirilmiş olarak, "Bellek yetersiz" gerçekten kullanılabilir bellek miktarının çok küçük olduğu anlamına gelmez. En yaygın neden, mevcut adres alanı içinde, istenen tahsise hizmet edecek kadar büyük bellek bölümünün olmamasıdır. Her biri 4 MB büyüklüğünde 100 bloğunuz varsa, bu 5 MB'lık bir bloğa ihtiyacınız olduğunda size yardımcı olmayacaktır.

Anahtar noktaları:

  • "işlem belleği" dediğimiz veri depolaması, bence en iyi disk üzerinde büyük bir dosya olarak görselleştirilir .
  • RAM yalnızca bir performans optimizasyonu olarak görülebilir
  • Programınızın tükettiği toplam sanal bellek miktarı, performansıyla gerçekten çok alakalı değil
  • "RAM'in tükenmesi" nadiren "bellek yetersiz" hatasına neden olur. Bir hata yerine, kötü performansa neden olur çünkü depolamanın aslında diskte olduğu gerçeğinin tüm maliyeti aniden alakalı hale gelir.

"Her biri 4 MB büyüklüğünde 100 bloğunuz varsa, bu 5 MB'lik bir bloğa ihtiyacınız olduğunda size yardımcı olmayacaktır" - Bence küçük bir düzeltmeyle daha iyi ifade edilir: "Eğer 100 " delik " bloğunuz varsa " .
OfirD

31

Visual Studio'nun varsayılan derleme modu olan 32 bit değil, 64 bitlik bir işlem oluşturduğunuzu kontrol edin. Bunu yapmak için projenize sağ tıklayın, Özellikler -> Yapı -> platform hedefi: x64. Herhangi bir 32 bit işlem gibi, 32 bit olarak derlenen Visual Studio uygulamalarının 2 GB sanal bellek sınırı vardır.

64 bitlik işlemler 64 bitlik işaretçiler kullandıklarından bu sınırlamaya sahip değildirler, bu nedenle teorik maksimum adres alanları (sanal belleklerinin boyutu) 16 eksabayttır (2 ^ 64). Gerçekte, Windows x64, işlemlerin sanal belleğini 8 TB ile sınırlar. Bellek sınırı sorununun çözümü daha sonra 64 bit olarak derlemektir.

Ancak, Visual Studio'daki nesnenin boyutu varsayılan olarak 2 GB ile sınırlıdır. Birleşik boyutu 2 GB'den büyük olan birkaç dizi oluşturabilirsiniz, ancak varsayılan olarak 2 GB'den büyük diziler oluşturamazsınız. Umarım, yine de 2 GB'den büyük diziler oluşturmak istiyorsanız, app.config dosyanıza aşağıdaki kodu ekleyerek bunu yapabilirsiniz:

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

Beni kurtardın. Teşekkür ederim!
Tee Zad Awk

25

762MB ayırmak için sürekli bir bellek bloğunuz yok, belleğiniz parçalanmış ve ayırıcı gerekli belleği ayırmak için yeterince büyük bir delik bulamıyor.

  1. / 3GB ile çalışmayı deneyebilirsiniz (diğerlerinin önerdiği gibi)
  2. Veya 64 bit işletim sistemine geçin.
  3. Veya algoritmayı büyük bir belleğe ihtiyaç duymayacak şekilde değiştirin. belki birkaç küçük (nispeten) bellek parçası ayırabilir.

8

Muhtemelen anladığınız gibi, sorun, bellek parçalanması nedeniyle çalışmayan büyük bir bitişik bellek bloğu ayırmaya çalışmanızdır. Yaptığınız şeyi yapmam gerekirse, şunları yapardım:

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

Ardından, kullanacağınız belirli bir dizini almak için randomNumbers[i / sizeB][i % sizeB].

Değerlere her zaman sırayla erişirseniz başka bir seçenek , tohumu belirtmek için aşırı yüklenmiş yapıcıyı kullanmak olabilir . Bu şekilde yarı rasgele bir sayı elde edersiniz (gibi DateTime.Now.Ticks) onu bir değişken içinde depolar, sonra listeye her başladığınızda orijinal tohumu kullanarak yeni bir Random örneği oluşturursunuz:

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

Fredrik Mörk'ün cevabında bağlantısı verilen blog, sorunun genellikle adres alanı eksikliğinden kaynaklandığını belirtmesine rağmen, 2GB CLR nesne boyutu sınırlaması gibi bir dizi başka sorunu listelemediğini belirtmek önemlidir ( ShuggyCoUk aynı blogda), bellek parçalanmasına değiniyor ve sayfa dosya boyutunun etkisinden (ve CreateFileMappingişlevin kullanılmasıyla nasıl ele alınabileceğinden) bahsetmiyor .

2GB sınırlaması, randomNumbers 2GB'den az olması gerektiği anlamına gelir . Diziler sınıflar olduklarından ve kendilerinin bir miktar ek yüküne sahip olduklarından, bu, dizinin double2 ^ 31'den küçük olması gerektiği anlamına gelir . Uzunluğun 2 ^ 31'den ne kadar küçük olması gerektiğinden emin değilim, ancak bir .NET dizisinin Ek yükü? 12 - 16 baytı gösterir.

Bellek parçalanması, HDD parçalanmasına çok benzer. 2GB adres alanınız olabilir, ancak nesneleri oluşturup yok ederken değerler arasında boşluklar olacaktır. Bu boşluklar büyük nesneniz için çok küçükse ve ek alan talep edilemiyorsa, o zaman alacaksınızSystem.OutOfMemoryException. Örneğin, 2 milyon 1024 baytlık nesne oluşturursanız, 1,9 GB kullanırsınız. Adresin 3'ün katı olmadığı her nesneyi silerseniz, .6GB bellek kullanırsınız, ancak adres alanı boyunca 2024 bayt açık bloklar arasında yayılır. Eğer .2GB büyüklüğünde bir nesne oluşturmanız gerekiyorsa, bunu yapamazsınız, çünkü onu sığdırmak için yeterince büyük bir blok yoktur ve ek alan elde edilemez (32 bitlik bir ortam varsayılarak). Bu sorunun olası çözümleri, daha küçük nesneler kullanmak, bellekte depoladığınız veri miktarını azaltmak veya bellek parçalanmasını sınırlamak / önlemek için bir bellek yönetimi algoritması kullanmak gibi şeylerdir. Büyük miktarda bellek kullanan büyük bir program geliştirmediğiniz sürece bunun bir sorun olmayacağına dikkat edilmelidir. Ayrıca,

Çoğu program işletim sisteminden çalışma belleği talep ettiğinden ve bir dosya eşleme istemediğinden, bunlar sistemin RAM'i ve sayfa dosya boyutu ile sınırlandırılacaktır. Blogda Néstor Sánchez'in (Néstor Sánchez) yaptığı yorumda belirtildiği gibi, C # gibi yönetilen bir kodla RAM / sayfa dosyası sınırlamasına ve işletim sisteminin adres alanına bağlı kalıyorsunuz.


Bu beklenenden çok daha uzundu. Umarım birine yardımcı olur. Bunu gönderdim çünkü System.OutOfMemoryExceptiondizimde sadece 2GB'lık şeyler olmasına rağmen 24GB RAM'li bir sistemde bir x64 programı çalıştırmaya başladım.


"Çoğu program işletim sisteminden çalışma belleği talep ettiğinden ve bir dosya eşleştirme talep etmediğinden" - Bunu açıklayan herhangi bir kaynağınız olur mu? Programlar, çalışma setlerinin bellekte yaşamasını gerektirdiğinde (ve RAM boyutuyla sınırlı olduğunda), programların diske yazabildiğinde ve bitişik bellekle sınırlı olduğunda (kabul edilen
yanıtçının

1
@uMdRupert Bunu araştırıp yazmayalı epey oldu, bu yüzden ek kaynağım yok. [CreateFileMapping function] ( msdn.microsoft.com/en-us/library/windows/desktop/aa366537.aspx ) bağlantısını okuma şansınız oldu mu? Ayrıca Paging üzerinde okumak isteyebilirsiniz .
Trisped

5

/ 3GB Windows önyükleme seçeneğine karşı tavsiye ederim. Dışında diğerlerinden (bunun için yapmasını overkill bir kötü davrandım uygulama ve büyük ihtimalle sorun Neyse çözmez), bu istikrarsızlık bir çok neden olabilir.

Pek çok Windows sürücüsü bu seçenekle test edilmemiştir, bu nedenle bunların birçoğu kullanıcı modu işaretçilerinin her zaman daha düşük 2GB adres alanına işaret ettiğini varsayar. Bu, / 3GB ile korkunç bir şekilde kırılabilecekleri anlamına gelir.

Ancak, Windows normalde 32 bitlik bir işlemi 2GB adres alanıyla sınırlar. Ancak bu, 2 GB tahsis edebileceğiniz anlamına gelmez!

Adres alanı zaten her türlü tahsis edilmiş veriyle doludur. Yığın ve yüklenen tüm derlemeler, statik değişkenler vb. Var. Herhangi bir yerde 800 MB bitişik ayrılmamış bellek olacağının garantisi yoktur.

2 400MB öbek ayırmak muhtemelen daha iyi sonuç verecektir. Veya 4 200MB yığın. Daha küçük ayırmalar, parçalanmış bir bellek alanında yer bulmak çok daha kolaydır.

Her neyse, bunu yine de 12GB'lık bir makineye dağıtacaksanız, bunu tüm sorunları çözmesi gereken 64 bitlik bir uygulama olarak çalıştırmak isteyeceksiniz.


İşi daha küçük parçalara bölmek de yukarıdaki güncellememi görmenize yardımcı olmuyor.
m3ntat

4

32 bitten 64 bit'e geçiş benim için çalıştı - 64 bit bir bilgisayardaysanız ve bağlantı noktasına gerek yoksa denemeye değer.



1

32bit pencerelerin 2GB işlem belleği sınırı vardır. Başkalarının bahsettiği / 3GB önyükleme seçeneği, bu 3 GB'ı, işletim sistemi çekirdeği kullanımı için yalnızca 1 gb ile yapacaktır. Gerçekçi bir şekilde 2GB'tan fazla uğraşmadan kullanmak istiyorsanız 64bit işletim sistemi gereklidir. Bu aynı zamanda, 4GB fiziksel RAM'e sahip olmanıza rağmen, video kartı için gerekli olan adres alanının bu belleğin oldukça büyük bir kısmını - genellikle yaklaşık 500MB - kullanılamaz hale getirebileceği problemin üstesinden gelir.


1

Devasa bir dizi ayırmak yerine, bir yineleyici kullanmayı deneyebilir misiniz? Bunlar gecikmeli olarak yürütülür, yani değerler yalnızca bir foreach ifadesinde talep edildiği gibi oluşturulur; Bu şekilde hafızanız bitmemelidir:

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

Yukarıdakiler istediğiniz kadar rastgele sayı üretecektir, ancak bunları yalnızca bir foreach ifadesiyle istenildiği şekilde üretecektir. Bu şekilde hafızanız tükenmez.

Alternatif olarak, hepsini tek bir yerde bulundurmanız gerekiyorsa, bunları hafıza yerine bir dosyada saklayın.


İlginç bir yaklaşım, ancak uygulamamın geri kalanının herhangi bir boşta kalma süresinde rastgele sayı deposu olarak olabildiğince fazla depolamam gerekiyor çünkü bu uygulama, birden fazla coğrafi bölgeyi (birden fazla monte carlo simülasyon çalışması) destekleyen 24 saatlik bir saat üzerinde çalışıyor, yaklaşık% 70 Günün maksimum cpu yükünün, gün boyunca kalan sürelerin tüm boş bellek alanında rastgele sayıları arabelleğe almak istiyorum. Diske kaydetme çok yavaş ve bu rasgele sayı bellek önbelleğine arabelleğe alma yapabileceğim herhangi bir kazanımı yok ediyor.
m3ntat

0

Büyük veri kümesiyle benzer bir sorun yaşıyorum ve uygulamayı bu kadar çok veri kullanmaya zorlamaya çalışmak gerçekten doğru seçenek değil. Size verebileceğim en iyi ipucu, verilerinizi mümkünse küçük parçalar halinde işlemektir. Bu kadar çok veriyle uğraştığımız için sorun er ya da geç geri gelecektir. Ayrıca, uygulamanızı çalıştıracak her makinenin yapılandırmasını bilemezsiniz, bu nedenle istisnanın başka bir bilgisayarda meydana gelmesi riski her zaman vardır.


Aslında makinenin konfigürasyonunu biliyorum, bu sadece bir sunucuda çalışıyor ve bunu o özellikler için yazabilirim. Bu, devasa bir monte carlo simülasyonu için ve rastgele sayıları önceden tamponlayarak optimize etmeye çalışıyorum.
m3ntat

0

Benzer bir sorun yaşadım, bunun nedeni bir StringBuilder.ToString ();


dizgi üreticisinin bu kadar büyümesine izin verme
Ricardo Rix

0

Çözümünüzü x64'e dönüştürün. Hala bir sorunla karşılaşırsanız, aşağıdaki gibi bir istisna oluşturan her şeye maksimum uzunluk verin:

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

0

Visual Studio Barındırma İşlemine ihtiyacınız yoksa:

Seçeneğin işaretini kaldırın: Proje-> Özellikler-> Hata Ayıkla-> Visual Studio Barındırma İşlemini Etkinleştir

Ve sonra inşa edin.

Hala sorunla karşılaşırsanız:

Proje-> Özellikler-> Olayları Oluştur-> Oluşturma Sonrası Olay Komut satırına gidin ve aşağıdakileri yapıştırın:

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

Şimdi projeyi inşa edin.


-2

Windows işlem sınırını 3 gb'ye yükseltin. (boot.ini veya Vista önyükleme yöneticisi aracılığıyla)


Gerçekten mi? varsayılan maksimum işlem belleği nedir? Ve nasıl değiştirilir? Bilgisayarımda bir oyun veya başka bir şey oynarsam, tek bir EXE / İşlemde 2+ GB'yi kolayca kullanabilir, buradaki sorunun bu olduğunu sanmıyorum.
m3ntat

/ 3GB bunun için aşırıdır ve birçok sürücü, kullanıcı alanı işaretçilerinin her zaman daha düşük 2GB'yi gösterdiğini varsaydığı için çok fazla kararsızlığa neden olabilir.
jalf

1
m3ntat: Hayır, 32 bit Windows'ta tek bir işlem 2 GB ile sınırlandırılmıştır. Kalan 2GB adres alanı çekirdek tarafından kullanılır.
jalf

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.