'System.OutOfMemoryException' було видано, коли ще багато вільної пам'яті


92

Це мій код:

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

Виняток: Виключено тип "System.OutOfMemoryException".

У мене є 4 Гб пам'яті на цій машині, 2,5 Гб вільно стартує, коли я запускаю це, на ПК явно достатньо місця для обробки 762 Мб 100000000 випадкових чисел. Мені потрібно зберегти якомога більше випадкових чисел з урахуванням доступної пам'яті. Коли я піду на виробництво, на коробці буде 12 Гб, і я хочу цим скористатися.

Чи обмежує CLR мене максимальною пам’яттю за замовчуванням для початку? і як я можу запитати більше?

Оновлення

Я думав, що розбиття цього на менші шматки та поступове додавання до моїх вимог до пам'яті допомогло б, якщо проблема пов’язана з фрагментацією пам’яті , але це не так, я не можу пройти повний розмір ArrayList 256 Мб незалежно від того, що я роблю, налаштовуючи 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()));                
  }
}

З мого основного методу:

int blockSize = 1000000;

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

6
Я рекомендую переархітектувати вашу програму, щоб вам не довелося використовувати стільки пам'яті. Що ви робите, що вам потрібно одразу сто мільйонів чисел у пам'яті?
Ерік Ліпперт,

2
Ви не відключили свій файл сторінки чи щось подібне дурне, правда?
jalf

@EricLippert, я стикаюся з цим, працюючи над проблемою P проти NP ( claymath.org/millenium-problems/p-vs-np-problem ). Чи маєте ви пропозицію щодо зменшення використання робочої пам’яті? (наприклад, серіалізація та зберігання фрагментів даних на жорсткому диску, використовуючи тип даних C ++ тощо)
devinbost

@bosit це сайт із запитаннями та відповідями. Якщо у вас є конкретне технічне запитання щодо фактичного коду, опублікуйте його як запитання.
Eric Lippert,

@bostIT посилання для проблеми P проти NP у вашому коментарі вже не дійсне.
RBT

Відповіді:


140

Можливо, ви захочете прочитати наступне: " „ Немає пам’яті "не стосується фізичної пам’яті " Еріка Ліпперта.

Коротше кажучи, і дуже спрощено, "Нестача пам'яті" насправді не означає, що обсяг доступної пам'яті занадто малий. Найбільш поширеною причиною є те, що в поточному адресному просторі немає суміжної частини пам'яті, яка є достатньо великою для обслуговування бажаного розподілу. Якщо у вас є 100 блоків, кожен розміром 4 МБ, це не допоможе вам, коли вам потрібен один блок розміром 5 МБ.

Ключові моменти:

  • зберігання даних, яке ми називаємо “пам’яттю процесів”, на мій погляд, найкраще візуалізується як масивний файл на диску .
  • Оперативна пам’ять може розглядатися як просто оптимізація продуктивності
  • Загальна кількість віртуальної пам’яті, яку споживає ваша програма, насправді не має великого значення для її продуктивності
  • "закінчення оперативної пам'яті" рідко призводить до помилки "нестачі пам'яті". Замість помилки це призводить до поганої роботи, оскільки повна вартість того, що пам’ять фактично знаходиться на диску, раптом стає актуальною.

"Якщо у вас є 100 блоків, кожен розміром 4 МБ, це не допоможе вам, коли вам знадобиться один блок розміром 5 МБ" - я думаю, це було б краще сформулювати із крихітною виправкою: "Якщо у вас є 100 " отворів " блоків" .
OfirD

31

Переконайтеся, що ви будуєте 64-розрядний процес, а не 32-розрядний, що є типовим режимом компіляції Visual Studio. Для цього клацніть правою кнопкою миші на своєму проекті, Властивості -> Збірка -> ціль платформи: x64. Як і будь-який 32-розрядний процес, програми Visual Studio, скомпільовані в 32-розрядної версії, мають обмеження віртуальної пам’яті 2 Гб.

64-розрядні процеси не мають цього обмеження, оскільки вони використовують 64-розрядні вказівники, тому їх теоретичний максимальний адресний простір (розмір віртуальної пам'яті) становить 16 ексабайт (2 ^ 64). Насправді Windows x64 обмежує віртуальну пам’ять процесів до 8 ТБ. Потім вирішення проблеми з обмеженням пам’яті полягає в компіляції в 64-біт.

Однак розмір об'єкта у Visual Studio за замовчуванням все ще обмежений 2 Гб. Ви зможете створити кілька масивів, загальний розмір яких перевищуватиме 2 Гб, але за замовчуванням ви не можете створювати масиви більше 2 ГБ. Сподіваємось, якщо ви все-таки хочете створити масиви, що перевищують 2 ГБ, ви можете це зробити, додавши наступний код до свого файлу app.config:

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

Ти врятував мене. Дякую!
Tee Zad Awk

25

У вас немає безперервного блоку пам'яті для виділення 762 МБ, ваша пам'ять фрагментована, і розподільник не може знайти достатньо великого отвору для виділення необхідної пам'яті.

  1. Ви можете спробувати працювати з / 3 ГБ (як пропонували інші)
  2. Або перейдіть на 64-бітну ОС.
  3. Або модифікуйте алгоритм, щоб йому не знадобився великий шматок пам'яті. можливо, виділити кілька менших (відносно) шматків пам'яті.

7

Як ви, напевно, зрозуміли, проблема полягає в тому, що ви намагаєтеся виділити один великий суміжний блок пам'яті, який не працює через фрагментацію пам'яті. Якби мені потрібно було робити те, що ти робиш, я б зробив наступне:

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

Потім, щоб отримати певний індекс, який ви б використовували randomNumbers[i / sizeB][i % sizeB].

Іншим варіантом, якщо ви завжди отримуєте доступ до значень по порядку, може бути використання перевантаженого конструктора для визначення насіння. Таким чином ви отримаєте напіввипадкове число (як-от DateTime.Now.Ticks), що зберігає його у змінній, а коли ви коли-небудь почнете переглядати список, ви створите новий випадковий екземпляр, використовуючи вихідне насіння:

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

Важливо зазначити, що хоча в блозі, на який посилається Фредрік Мерк, вказується, що проблема, як правило, пов’язана з браком адресного простору, в ній не вказано низку інших проблем, таких як обмеження розміру об’єкта CLR на 2 ГБ (згадане у коментарі від ShuggyCoUk у тому самому блозі), зачіпає фрагментацію пам’яті та не згадує про вплив розміру файлу сторінки (і про те, як з ним можна вирішити за допомогою CreateFileMappingфункції ).

Обмеження 2 ГБ означає, що воно randomNumbers має бути менше 2 ГБ. Оскільки масиви є класами і мають деякі накладні витрати, це означає, що масив doubleповинен бути меншим за 2 ^ 31. Я не впевнений, наскільки меншою, ніж 2 ^ 31, повинна бути довжина, але накладні витрати на масив .NET? вказує 12 - 16 байт.

Фрагментація пам'яті дуже схожа на фрагментацію HDD. Можливо, у вас є 2 ГБ адресного простору, але під час створення та знищення об’єктів між значеннями будуть прогалини. Якщо ці прогалини занадто малі для вашого великого об'єкта, і додатковий простір не можна вимагати, тоді ви отримаєтеSystem.OutOfMemoryException. Наприклад, якщо ви створюєте 2 мільйони 1024 байтових об’єктів, тоді ви використовуєте 1,9 ГБ. Якщо ви видалите кожен об'єкт, адреса якого не кратна 3, тоді ви будете використовувати .6 ГБ пам'яті, але він буде розподілений по адресному простору з 2024 байтними відкритими блоками між ними. Якщо вам потрібно створити об’єкт розміром .2 ГБ, ви не зможете цього зробити, оскільки немає блоку, достатньо великого, щоб вмістити його, і додатковий простір отримати не вдається (при умові 32-бітового середовища). Можливими рішеннями цієї проблеми є такі речі, як використання менших об'єктів, зменшення обсягу даних, які ви зберігаєте в пам'яті, або використання алгоритму управління пам'яттю для обмеження / запобігання фрагментації пам'яті. Слід зазначити, що якщо ви не розробляєте велику програму, яка використовує великий обсяг пам'яті, це не буде проблемою. Крім того,

Оскільки більшість програм вимагають робочої пам'яті від ОС і не вимагають відображення файлів, вони будуть обмежені оперативною пам’яттю системи та розміром файлу сторінки. Як зазначається в коментарі Нестора Санчеса (Néstor Sánchez) у блозі, з керованим кодом, таким як C #, ви застрягли в обмеженні ОЗУ / файлів сторінок та адресному просторі операційної системи.


Це було набагато довше, ніж очікувалося. Сподіваємось, це комусь допомагає. Я опублікував його, тому що зіткнувся із System.OutOfMemoryExceptionзапущеною програмою x64 в системі з 24 Гб оперативної пам'яті, хоча мій масив містив лише 2 Гб речей.


5

Я б не радив використовувати параметр завантаження Windows / 3GB. Окрім усього іншого (робити це надмірно для однієї програми з поганою поведінкою, і це, мабуть, все одно не вирішить вашу проблему) може спричинити велику нестабільність.

Багато драйверів Windows не тестуються за допомогою цієї опції, тому чимало з них припускають, що вказівники в режимі користувача завжди вказують на нижчі 2 Гб адресного простору. А це означає, що вони можуть жахливо порвати з / 3GB.

Однак Windows зазвичай обмежує 32-розрядний процес до адресного простору 2 Гб. Але це не означає, що слід сподіватися на можливість виділити 2 Гб!

Адресний простір вже заповнений усіма видами виділених даних. Там є стек і всі завантажувані збірки, статичні змінні тощо. Немає гарантії, що де-небудь буде 800 Мб суміжної нерозподіленої пам'яті.

Виділення 2 шматків на 400 Мб, мабуть, було б краще. Або 4 шматки 200 Мб. Для менших розподілів набагато простіше знайти місце у фрагментованому просторі пам'яті.

У будь-якому випадку, якщо ви все одно збираєтесь розгорнути це на машині обсягом 12 ГБ, ви захочете запустити його як 64-розрядну програму, яка повинна вирішити всі проблеми.


Розбиття роботи на менші шматки, здається, не допомагає побачити моє оновлення вище.
m3ntat

4

Зміна з 32 на 64 біт спрацювала для мене - варто спробувати, якщо ви перебуваєте на 64 бітному ПК і йому не потрібно переносити порт.



1

32-бітове вікно має обмеження в 2 ГБ оперативної пам'яті. Варіант завантаження / 3 Гб, про який згадали інші, зробить цей 3 Гб лише 1 Гб, що залишиться для використання ядра ОС. Реально, якщо ви хочете використовувати більше 2 ГБ без клопоту, потрібна 64-бітна ОС. Це також долає проблему, через яку, хоча у вас є 4 ГБ фізичної оперативної пам'яті, адресовий простір, необхідний для відеокарти, може зробити значний патрон цієї пам'яті непридатним - зазвичай близько 500 МБ.


1

Замість виділення масивного масиву, чи не могли б ви спробувати використати ітератор? Вони виконуються із затримкою, тобто значення генеруються лише за запитом у операторі foreach; вам не слід закінчувати пам'ять таким чином:

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

Вищезазначене генерує скільки завгодно випадкових чисел, але генерує їх лише за запитом за допомогою оператора foreach. У вас таким чином не залишиться пам'яті.

Крім того, якщо ви повинні мати їх усі в одному місці, зберігайте їх у файлі, а не в пам'яті.


Ітерестуючий підхід, але мені потрібно зберігати якомога більше як сховище випадкових чисел у будь-який час простою решти моєї програми, оскільки цей додаток працює цілодобово, підтримуючи кілька географічних регіонів (багаторазові запуски моделювання Монте Карло), близько 70% максимального завантаження процесора за день, час, що залишився протягом дня, я хочу буферизувати випадкові числа у всьому вільному просторі пам'яті. Зберігання на диск занадто повільне, і це перешкоджає будь-яким виграшам, які я міг би зробити, буферизуючи цей кеш пам'яті випадкових чисел.
m3ntat

0

Ну, я маю подібну проблему з великим набором даних, і спроба змусити програму використовувати стільки даних насправді не є правильним варіантом. Найкраща порада, яку я можу вам дати, - це обробляти ваші дані невеликими шматками, якщо це можливо. Оскільки, маючи справу з великою кількістю даних, проблема рано чи пізно повернеться. Крім того, ви не можете знати конфігурацію кожного комп'ютера, на якому буде запущена ваша програма, тому завжди існує ризик, що виняток відбудеться на іншому ПК.


Насправді я знаю конфігурацію машини, вона працює лише на одному сервері, і я можу написати це для цих специфікацій. Це для масового моделювання Монте-Карло, і я намагаюся оптимізувати за допомогою буферизації випадкових чисел заздалегідь.
m3ntat


0

Перетворіть своє рішення на x64. Якщо проблема все-таки стикається, надайте максимальну довжину всьому, що створює виняток, як показано нижче:

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

0

Якщо вам не потрібен процес хостингу Visual Studio:

Зніміть прапорець: Проект-> Властивості-> Налагодження-> Увімкнути процес хостингу Visual Studio

А потім будувати.

Якщо проблема все ще стикається:

Перейдіть до Проект-> Властивості-> Події побудови-> Командний рядок події після побудови та вставте наступне:

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

Тепер побудуйте проект.


-2

Збільште обмеження процесу Windows до 3 Гб. (через boot.ini або менеджер завантаження Vista)


справді? яка за замовчуванням максимальна пам’ять процесу? І як це змінити? Якщо я граю в якусь гру або щось на своєму ПК, він може легко використати 2+ ГБ від одного EXE / процесу, я не думаю, що тут проблема.
m3ntat

/ 3 Гб це надмірно, і це може спричинити велику нестабільність, оскільки багато драйверів припускають, що покажчики простору користувача завжди вказують на нижчі 2 Гб.
jalf

1
m3ntat: Ні, у 32-розрядної Windows один процес обмежений 2 Гб. Решта 2 Гб адресного простору використовується ядром.
jalf

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.