'System.OutOfMemoryException' muncul saat masih ada banyak memori kosong

93

Ini kode saya:

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

Pengecualian: Pengecualian jenis 'System.OutOfMemoryException' dilempar.

Saya memiliki memori 4GB pada mesin ini. 2,5GB kosong ketika saya mulai menjalankan ini, jelas ada cukup ruang pada PC untuk menangani 762mb dari 100000000 nomor acak. Saya perlu menyimpan sebanyak mungkin nomor acak mengingat memori yang tersedia. Ketika saya pergi ke produksi akan ada 12GB di kotak dan saya ingin memanfaatkannya.

Apakah CLR membatasi saya ke memori maks default untuk memulai? dan bagaimana cara meminta lebih banyak?

Memperbarui

Saya pikir memecah ini menjadi potongan-potongan yang lebih kecil dan secara bertahap menambahkan ke persyaratan memori saya akan membantu jika masalahnya disebabkan oleh fragmentasi memori , tetapi ternyata saya tidak dapat melewati ukuran ArrayList total 256mb terlepas dari apa yang saya lakukan tweaking 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()));                
  }
}

Dari metode utama saya:

int blockSize = 1000000;

while (true) {
  try
  {
    AddNDRandomNumbers(blockSize);                    
  }
  catch (System.OutOfMemoryException ex)
  {
    break;
  }
}            
double arrayTotalSizeInMegabytes = (ndRandomNumbers.Count * 8.0) / 1024.0 / 1024.0;
m3ntat
sumber
6
Saya akan merekomendasikan pengarsipan ulang aplikasi Anda sehingga Anda tidak perlu menggunakan banyak memori. Apa yang Anda lakukan sehingga Anda membutuhkan seratus juta nomor sekaligus dalam memori?
Eric Lippert
2
Anda belum menonaktifkan pagefile Anda atau sesuatu yang konyol seperti itu, bukan?
jalf
@EricLippert, Saya mengalami hal ini saat mengerjakan masalah P vs. NP ( claymath.org/millenium-problems/p-vs-np-problem ). Apakah Anda punya saran untuk mengurangi penggunaan memori kerja? (misalnya membuat serial dan menyimpan potongan data pada hard disk, menggunakan tipe data C ++, dll.)
devinbost
@bosit ini adalah situs tanya jawab. Jika Anda memiliki pertanyaan teknis khusus tentang kode sebenarnya, posting sebagai pertanyaan.
Eric Lippert
@bostIT tautan untuk masalah P vs. NP di komentar Anda tidak valid lagi.
RBT

Jawaban:

142

Anda mungkin ingin membaca ini: " " Out Of Memory "Tidak Merujuk ke Memori Fisik " oleh Eric Lippert.

Singkatnya, dan sangat disederhanakan, "Memori habis" tidak berarti jumlah memori yang tersedia terlalu kecil. Alasan paling umum adalah bahwa dalam ruang alamat saat ini, tidak ada bagian memori yang berdekatan yang cukup besar untuk melayani alokasi yang diinginkan. Jika Anda memiliki 100 blok, masing-masing berukuran 4 MB, itu tidak akan membantu Anda ketika Anda membutuhkan satu blok 5 MB.

Poin Utama:

  • penyimpanan data yang kami sebut "memori proses" menurut saya paling baik divisualisasikan sebagai file besar pada disk .
  • RAM dapat dilihat hanya sebagai pengoptimalan kinerja
  • Jumlah total memori virtual yang digunakan program Anda sebenarnya tidak terlalu relevan dengan kinerjanya
  • "kehabisan RAM" jarang menghasilkan kesalahan "kehabisan memori". Alih-alih kesalahan, itu menghasilkan kinerja yang buruk karena biaya penuh dari fakta bahwa penyimpanan sebenarnya pada disk tiba-tiba menjadi relevan.
Fredrik Mörk
sumber
"Jika Anda memiliki 100 blok, masing-masing 4 MB besar, itu tidak akan membantu Anda saat Anda membutuhkan satu blok 5 MB" - Saya pikir akan lebih baik diungkapkan dengan koreksi kecil: "Jika Anda memiliki 100 blok " lubang " " .
OfirD
31

Periksa bahwa Anda sedang membuat proses 64-bit, dan bukan 32-bit, yang merupakan mode kompilasi default Visual Studio. Untuk melakukan ini, klik kanan pada proyek Anda, Properties -> Build -> platform target: x64. Seperti proses 32-bit apa pun, aplikasi Visual Studio yang dikompilasi dalam 32-bit memiliki batas memori virtual 2GB.

Proses 64-bit tidak memiliki batasan ini, karena mereka menggunakan pointer 64-bit, sehingga ruang alamat maksimum teoritisnya (ukuran memori virtualnya) adalah 16 exabyte (2 ^ 64). Pada kenyataannya, Windows x64 membatasi memori virtual proses hingga 8TB. Solusi untuk masalah batas memori kemudian dikompilasi dalam 64-bit.

Namun, ukuran objek di Visual Studio masih dibatasi hingga 2GB, secara default. Anda akan dapat membuat beberapa array yang ukuran gabungannya akan lebih besar dari 2GB, tetapi Anda tidak dapat secara default membuat array lebih besar dari 2GB. Mudah-mudahan, jika Anda masih ingin membuat array yang lebih besar dari 2GB, Anda bisa melakukannya dengan menambahkan kode berikut ke file app.config Anda:

<configuration>
  <runtime>
    <gcAllowVeryLargeObjects enabled="true" />
  </runtime>
</configuration>
Teknologi Pergeseran
sumber
Kamu menyelamatkanku. Terima kasih!
Tee Zad Awk
25

Anda tidak memiliki blok memori berkelanjutan untuk mengalokasikan 762MB, memori Anda terfragmentasi dan pengalokasi tidak dapat menemukan lubang yang cukup besar untuk mengalokasikan memori yang dibutuhkan.

  1. Anda dapat mencoba untuk bekerja dengan / 3GB (seperti yang disarankan orang lain)
  2. Atau beralih ke OS 64 bit.
  3. Atau ubah algoritme sehingga tidak membutuhkan banyak memori. mungkin mengalokasikan beberapa potongan memori yang lebih kecil (relatif).
Shay Erlichmen
sumber
7

Seperti yang mungkin Anda ketahui, masalahnya adalah Anda mencoba mengalokasikan satu blok memori yang berdekatan, yang tidak berfungsi karena fragmentasi memori. Jika saya perlu melakukan apa yang Anda lakukan, saya akan melakukan hal berikut:

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

Kemudian, untuk mendapatkan indeks tertentu yang akan Anda gunakan randomNumbers[i / sizeB][i % sizeB].

Pilihan lain jika Anda selalu mengakses nilai secara berurutan mungkin menggunakan konstruktor kelebihan beban untuk menentukan seed. Dengan cara ini Anda akan mendapatkan nomor semi acak (seperti DateTime.Now.Ticks) menyimpannya dalam variabel, lalu kapan pun Anda mulai menelusuri daftar, Anda akan membuat instance Random baru menggunakan seed asli:

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

Penting untuk dicatat bahwa meskipun blog yang ditautkan dalam jawaban Fredrik Mörk menunjukkan bahwa masalah tersebut biasanya disebabkan oleh kurangnya ruang alamat , blog tersebut tidak mencantumkan sejumlah masalah lain, seperti batasan ukuran objek CLR 2GB (disebutkan dalam komentar dari ShuggyCoUk di blog yang sama), menyoroti fragmentasi memori, dan gagal menyebutkan dampak ukuran file halaman (dan bagaimana hal itu dapat diatasi dengan penggunaan CreateFileMappingfungsi ).

Batasan 2GB artinya randomNumbers harus kurang dari 2GB. Karena array adalah kelas dan memiliki beberapa overhead sendiri, ini berarti array doubleharus lebih kecil dari 2 ^ 31. Saya tidak yakin berapa jauh lebih kecil dari 2 ^ 31 Panjangnya harus, tetapi Overhead dari array NET.? menunjukkan 12 - 16 byte.

Fragmentasi memori sangat mirip dengan fragmentasi HDD. Anda mungkin memiliki ruang alamat 2GB, tetapi saat Anda membuat dan menghancurkan objek, akan ada celah di antara nilai-nilai tersebut. Jika celah ini terlalu kecil untuk objek besar Anda, dan ruang tambahan tidak dapat diminta, Anda akan mendapatkanSystem.OutOfMemoryException. Misalnya, jika Anda membuat 2 juta, 1024 objek byte, maka Anda menggunakan 1.9GB. Jika Anda menghapus setiap objek yang alamatnya bukan kelipatan 3 maka Anda akan menggunakan memori .6GB, tetapi akan tersebar di seluruh ruang alamat dengan blok terbuka 2024 byte di antaranya. Jika Anda perlu membuat objek berukuran .2GB, Anda tidak akan dapat melakukannya karena tidak ada blok yang cukup besar untuk memuatnya dan ruang tambahan tidak dapat diperoleh (dengan asumsi lingkungan 32 bit). Solusi yang mungkin untuk masalah ini adalah hal-hal seperti menggunakan objek yang lebih kecil, mengurangi jumlah data yang Anda simpan di memori, atau menggunakan algoritma manajemen memori untuk membatasi / mencegah fragmentasi memori. Perlu dicatat bahwa kecuali Anda mengembangkan program besar yang menggunakan banyak memori, ini tidak akan menjadi masalah. Juga,

Karena sebagian besar program meminta memori kerja dari OS dan tidak meminta pemetaan file, program tersebut akan dibatasi oleh RAM sistem dan ukuran file halaman. Seperti dicatat dalam komentar oleh Néstor Sánchez (Néstor Sánchez) di blog, dengan kode terkelola seperti C # Anda terjebak pada batasan file RAM / halaman dan ruang alamat sistem operasi.


Itu jauh lebih lama dari yang diharapkan. Semoga membantu seseorang. Saya mempostingnya karena saya System.OutOfMemoryExceptionmenjalankan program x64 pada sistem dengan RAM 24GB meskipun array saya hanya menyimpan barang 2GB.

Dicoret
sumber
5

Saya menyarankan agar opsi boot windows / 3GB. Terlepas dari yang lainnya (terlalu berlebihan untuk melakukan ini untuk satu aplikasi yang berperilaku buruk, dan itu mungkin tidak akan menyelesaikan masalah Anda), ini dapat menyebabkan banyak ketidakstabilan.

Banyak driver Windows tidak diuji dengan opsi ini, jadi beberapa dari mereka menganggap bahwa penunjuk mode pengguna selalu mengarah ke 2GB ruang alamat yang lebih rendah. Yang berarti mereka mungkin rusak parah dengan / 3GB.

Namun, Windows biasanya membatasi proses 32-bit ke ruang alamat 2GB. Tetapi itu tidak berarti Anda harus berharap dapat mengalokasikan 2GB!

Ruang alamat sudah dikotori dengan semua jenis data yang dialokasikan. Ada tumpukan, dan semua rakitan yang dimuat, variabel statis, dan sebagainya. Tidak ada jaminan bahwa akan ada 800MB memori yang tidak terisi di mana saja.

Mengalokasikan 2.400MB potongan mungkin akan berjalan lebih baik. Atau 4 potongan 200MB. Alokasi yang lebih kecil jauh lebih mudah untuk menemukan ruang dalam ruang memori yang terfragmentasi.

Bagaimanapun, jika Anda akan menerapkan ini ke mesin 12GB, Anda pasti ingin menjalankan ini sebagai aplikasi 64-bit, yang seharusnya menyelesaikan semua masalah.

jalf
sumber
Membagi pekerjaan menjadi bagian yang lebih kecil sepertinya tidak membantu melihat pembaruan saya di atas.
m3ntat
4

Mengubah dari 32 menjadi 64 bit berhasil untuk saya - patut dicoba jika Anda menggunakan pc 64 bit dan tidak perlu port.

chris
sumber
1

Jendela 32bit memiliki batas memori proses 2GB. Opsi boot / 3GB yang disebutkan orang lain akan membuat 3GB ini dengan hanya tersisa 1GB untuk penggunaan kernel OS. Secara realistis jika ingin menggunakan lebih dari 2GB tanpa ribet maka diperlukan OS 64bit. Ini juga mengatasi masalah di mana meskipun Anda mungkin memiliki RAM fisik 4GB, ruang alamat yang diperlukan untuk kartu video dapat membuat potongan yang cukup besar dari memori itu tidak dapat digunakan - biasanya sekitar 500MB.

redcalx
sumber
1

Daripada mengalokasikan array yang sangat besar, dapatkah Anda mencoba menggunakan iterator? Ini adalah penundaan-dieksekusi, artinya nilai-nilai dihasilkan hanya seperti yang diminta dalam pernyataan foreach; Anda tidak boleh kehabisan memori dengan cara ini:

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

Di atas akan menghasilkan nomor acak sebanyak yang Anda inginkan, tetapi hanya menghasilkannya seperti yang diminta melalui pernyataan foreach. Anda tidak akan kehabisan memori dengan cara itu.

Bergantian, Jika Anda harus memiliki semuanya di satu tempat, simpanlah dalam file, bukan di memori.

Judah Gabriel Himango
sumber
Pendekatan Iteresting tetapi saya perlu menyimpan sebanyak mungkin sebagai repositori nomor acak dalam waktu menganggur sisa aplikasi saya karena aplikasi ini berjalan pada jam 24 jam yang mendukung beberapa wilayah geografis (beberapa simulasi monte carlo berjalan), sekitar 70% hari max cpu load, sisa waktu sepanjang hari saya ingin buffer nomor acak di semua ruang memori kosong. Menyimpan ke disk terlalu lambat dan jenis mengalahkan keuntungan apa pun yang bisa saya buat buffering ke dalam cache memori nomor acak ini.
m3ntat
0

Nah, saya mendapat masalah serupa dengan kumpulan data yang besar dan mencoba memaksa aplikasi untuk menggunakan begitu banyak data bukanlah pilihan yang tepat. Tip terbaik yang dapat saya berikan adalah memproses data Anda dalam potongan kecil jika memungkinkan. Karena berurusan dengan begitu banyak data, masalah akan kembali cepat atau lambat. Selain itu, Anda tidak dapat mengetahui konfigurasi setiap mesin yang akan menjalankan aplikasi Anda sehingga selalu ada risiko pengecualian akan terjadi pada komputer lain.

Francis B.
sumber
Sebenarnya saya tahu konfigurasi mesin, ini berjalan di satu server saja dan saya dapat menulis ini untuk spesifikasi tersebut. Ini untuk simulasi monte carlo besar-besaran dan saya mencoba mengoptimalkan dengan menyangga nomor acak di muka.
m3ntat
0

Saya memiliki masalah serupa, itu karena StringBuilder.ToString ();

Ricardo Rix
sumber
jangan biarkan pembuat tali menjadi begitu besar
Ricardo Rix
0

Ubah solusi Anda menjadi x64. Jika Anda masih menghadapi masalah, berikan panjang maksimal untuk semua yang memiliki pengecualian seperti di bawah ini:

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

Jika Anda tidak memerlukan Proses Hosting Visual Studio:

Hapus centang opsi: Project-> Properties-> Debug-> Aktifkan Proses Hosting Visual Studio

Dan kemudian membangun.

Jika Anda masih menghadapi masalah:

Pergi ke Project-> Properties-> Build Events-> Post-Build Event Command line dan tempel yang berikut ini:

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

Sekarang, bangun proyeknya.

Yasir Arafat
sumber
-2

Tingkatkan batas proses Windows menjadi 3gb. (melalui boot.ini atau manajer boot Vista)

leppie
sumber
Betulkah? apa memori proses maks default? Dan bagaimana cara mengubahnya? Jika saya memainkan game atau sesuatu di PC saya, ia dapat dengan mudah menggunakan 2+ GB dari satu EXE / Proses, saya rasa ini bukan masalahnya.
m3ntat
/ 3GB berlebihan untuk ini, dan dapat menyebabkan banyak ketidakstabilan karena banyak driver berasumsi bahwa petunjuk ruang pengguna selalu mengarah ke 2GB yang lebih rendah.
jalf
1
m3ntat: Tidak, di Windows 32-bit, satu proses dibatasi hingga 2GB. Sisa 2GB dari ruang alamat digunakan oleh kernel.
jalf