'System.OutOfMemoryException' было выброшено, когда еще достаточно свободной памяти


93

Это мой код:

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 vs. NP ( Claymath.org/millenium-problems/p-vs-np-problem ). У вас есть предложения по сокращению использования рабочей памяти? (например, сериализация и хранение фрагментов данных на жестком диске с использованием типа данных C ++ и т. д.)
devinbost 09

@bosit это сайт вопросов и ответов. Если у вас есть конкретный технический вопрос о реальном коде, опубликуйте его как вопрос.
Эрик Липперт

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

Ответы:


142

Вы можете прочитать это: « Недостаточно памяти» не относится к физической памяти »Эрика Липперта.

Короче говоря, и очень упрощенно, «Недостаточно памяти» на самом деле не означает, что объем доступной памяти слишком мал. Наиболее частая причина заключается в том, что в текущем адресном пространстве нет непрерывной части памяти, достаточно большой для обслуживания желаемого распределения. Если у вас 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. Вы можете попробовать работать с / 3GB (как предлагали другие)
  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 байтов.

Фрагментация памяти очень похожа на фрагментацию жесткого диска. У вас может быть 2 ГБ адресного пространства, но по мере создания и уничтожения объектов между значениями будут промежутки. Если эти зазоры слишком малы для вашего большого объекта, и дополнительное пространство не может быть запрошено, тогда вы получитеSystem.OutOfMemoryException. Например, если вы создаете 2 миллиона объектов по 1024 байта, вы используете 1,9 ГБ. Если вы удалите каждый объект, адрес которого не кратен 3, вы будете использовать 0,6 ГБ памяти, но он будет распределен по адресному пространству с 2024-байтовыми открытыми блоками между ними. Если вам нужно создать объект размером 0,2 ГБ, вы не сможете это сделать, потому что нет блока, достаточно большого для его размещения, и невозможно получить дополнительное пространство (при условии 32-битной среды). Возможные решения этой проблемы - это использование более мелких объектов, уменьшение объема данных, которые вы храните в памяти, или использование алгоритма управления памятью для ограничения / предотвращения фрагментации памяти. Следует отметить, что если вы не разрабатываете большую программу, которая использует большой объем памяти, это не будет проблемой. Также,

Поскольку большинство программ запрашивают рабочую память у ОС и не запрашивают сопоставление файлов, они будут ограничены оперативной памятью системы и размером файла подкачки. Как отмечено в комментарии Нестора Санчеса (Néstor Sánchez) в блоге, с управляемым кодом, таким как C #, вы привязаны к ограничению RAM / файла страницы и адресному пространству операционной системы.


Это было намного дольше, чем ожидалось. Надеюсь, это кому-то поможет. Я разместил его, потому что я столкнулся с 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 ГБ. Вариант загрузки / 3GB, о котором упоминали другие, сделает эти 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:

Снимите флажок с опции: Project-> Properties-> Debug-> Enable the Visual Studio Hosting Process.

А потом строить.

Если проблема не исчезла:

Перейдите в Project-> Properties-> Build Events-> Командная строка события после сборки и вставьте следующее:

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

Теперь создайте проект.


-2

Увеличьте ограничение процесса Windows до 3 ГБ. (через boot.ini или диспетчер загрузки Vista)


действительно? какова максимальная память процесса по умолчанию? А как это поменять? Если я играю в игру или что-то еще на своем ПК, он может легко использовать 2+ ГБ на один EXE / процесс, я не думаю, что это проблема.
m3ntat

/ 3GB для этого слишком много и может вызвать большую нестабильность, поскольку многие драйверы предполагают, что указатели пользовательского пространства всегда указывают на нижние 2GB.
jalf

1
m3ntat: Нет, в 32-битной Windows размер одного процесса ограничен 2 ГБ. Оставшиеся 2 ГБ адресного пространства используются ядром.
jalf

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.