Форум: "Основная";
Текущий архив: 2005.06.29;
Скачать: [xml.tar.bz2];
ВнизОшибка "Out of memory" Найти похожие ветки
← →
TrueCoder (2005-06-01 03:29) [0]Так как Delphi пользуюсь нечасто, возникла проблема, решить которую сам пока не могу. Поиск тоже ничего что-то не дал..
Объявлен тип, например: type TValues = array [0..8] of Variant;
Объявлен массив: Values: array of TValues;
Имеется счетчик элементов массива Values = Count: Integer.
Изначально массив пуст, значит Count=0.
Новые элементы массива Values добавляются в цикле по одному через SetLength(Values,Count+1). И все работает, до тех пор пока размер памяти, занимаемый приложением, не достигает 128 мегабайт. После чего, возникает ошибка "Out of memory".
Причем, размер оперативки в компе - 1 Гб, не говоря уже о виртуальной памяти. В момент возникновения ошибки оперативки свободно около 500 Мб. Выгрузка вообще всех других приложений тоже не помогает.
Вопрос - отчего возникает такая ошибка? Может я чего не понимаю в механизме выделения памяти? В хелпе имеется такая фраза по SetLength - "If there is not enough memory available to reallocate the variable, SetLength raises an EOutOfMemory exception", что у меня, похоже, и происходит. Но почему?!
ОС - WinXP Home, сама Delphi - семерка. Благодарю за любую помощь.
← →
Defunct © (2005-06-01 04:35) [1]Попробуйте заменить массив на TList, там SetLength используется немного не так как у вас.
← →
Digitman © (2005-06-01 08:45) [2]при очередном запросе Вorland Мemory Мanager"а на перераспределение памяти система не обнаружила в АП тек.процесса свободного региона затребованного размера и вернула отказ, о чем ВММ любезно и сообщил соотв.исключением
скорей всего АП процесса на момент отказа было сильно фрагментировано по причине предыдущих массированных "мелких" реаллокаций, инициированных вызовами SetLength()
← →
ANB © (2005-06-01 08:57) [3]
> Digitman © (01.06.05 08:45) [2]
Имхо. Очень странно. Занято 128М. Дефрагментация, понятно, но у процесса 4Г адресного пространства. Как может не хватить ? Может проблема в стеке ?
← →
Anatoly Podgoretsky © (2005-06-01 09:06) [4]TrueCoder (01.06.05 03:29)
Где код?
← →
Bel © (2005-06-01 09:09) [5]> но у процесса 4Г адресного пространства
Причем, учти, из них половина отдана под ядро, а в оставшейся половине кроме данных твоего приложения располагаются exe-шник твоего приложения, а также все библиотеки. Плюс много других данных, используемых приложением. В итоге остается достаточно свободной памяти, но если расходовать ее неправильно, то и она может кончиться.
← →
ANB © (2005-06-01 09:16) [6]
> Причем, учти, из них половина отдана под ядро
- Ну 2Г. Ну 500М накладные расходы (сроду столько не было). 1,5Г забить одним массивом ?
В принципе хороший совет - сначала определить максимальную длинну, а потом писать в массив, но он не всегда выполним. Например, я не всегда знаю заранее размер массива. В своем проекте у меня много мест, где длинна приращивается на 1, но на такую ошибку ни разу не нарывался. Ресурсы раз кончаться начали, но это я компонент кривой заюзал. Поменял на стандартный - ошибка ушла.
← →
Bel © (2005-06-01 09:21) [7]Дык, массив то должен занимать непрерывный участок памяти. Памяти то может быть и 1.5Г свободно, а вот непрерывного участка размером, допустим, 10М может не быть. Поэтому в данном случае уже предложили решение - использовать TList.
← →
evvcom © (2005-06-01 09:22) [8]Вообще подход действительно неверный. Реаллокэйтить такие массивы - это встретиться с жуткими тормозами из-за постоянного копирования "полезной" информации. Лучше использовать TList или цепочки.
← →
Digitman © (2005-06-01 09:22) [9]
> ANB © (01.06.05 08:57) [3]
> Занято 128М
эта цифирь не имеет отношения к ВАП процесса
← →
ЮЮ © (2005-06-01 09:32) [10]Если человеку не нужны механизмы TList, то достаточно увеличивать длину массива так, как это сделано в TList.Grow, тем более, что количество действительных элементов массива он и так подсчитывает в Count
← →
ANB © (2005-06-01 09:32) [11]
> эта цифирь не имеет отношения к ВАП процесса
- согласен. Но не полностью. Чем больше эта цифра, тем больше вероятности нарваться на конец памяти. Приплюсовать сюда дефрагментацию . . . Кстати, автор не дал код и не сообщил максимальную длинну своего массива.
← →
Mx © (2005-06-01 09:46) [12]Возник вопрос: а чем же TList лучше? В не тоже есть Realloc"и, хоть и реже вызываемые, но в итоге точно также потребуется большой объем памяти (правда Pointer меньше чем Variant, но это лишь вопрос количества элементов).
← →
Думкин © (2005-06-01 09:48) [13]> [12] Mx © (01.06.05 09:46)
хоть и реже вызываемые, - ты сказал(с)
← →
Mx © (2005-06-01 09:55) [14]
> Думкин © (01.06.05 09:48) [13]
Вроде ошибка: Out of memory, а не Unable to Realloc? Не хватает, как я понял, объема, причем здесь количество реалокейтов?
← →
ANB © (2005-06-01 09:56) [15]Без кода и поллитры не разберемся.
← →
Думкин © (2005-06-01 10:05) [16]> [14] Mx © (01.06.05 09:55)
смотрим [2] Digitman © (01.06.05 08:45) - задумываемся.
← →
Mx © (2005-06-01 10:13) [17]
> Думкин © (01.06.05 10:05) [16]
Я уже читал. Но: Дык, массив то должен занимать непрерывный участок памяти. Памяти то может быть и 1.5Г свободно, а вот непрерывного участка размером, допустим, 10М может не быть. Поэтому в данном случае уже предложили решение - использовать TList. © Bel. Ну допустим, Realloc не смог выделить память в текущем месте, я так понимаю занимаемую память он освободит, и будет выделен новый - бОльший участок в другом месте. Ну так и причем здесь количество Realloc"ов? Или он работает только "по возрастанию"? Т.е. в конце концов может "упереться в вершину" (извините, за образность)?
← →
evvcom © (2005-06-01 10:17) [18]
> Mx © (01.06.05 10:13) [17]
Каков характер твоих данных? Необходим ли тебе доступ к произвольному элементу этого массива в любой момент времени? Или эти данные обрабатываются строго по порядку (сверху вниз или снизу вверх, не важно)?
← →
Mx © (2005-06-01 10:20) [19]У меня данных нет, почитал ветку и кое чего не понял, вот и спросил... вдогонку.
← →
evvcom © (2005-06-01 10:24) [20]Да, действительно... [18] должно быть адресовано TrueCoder
← →
Mx © (2005-06-01 10:31) [21]Продолжаю. Допустим у нас 20 ячеек памяти, каждая пятая занята. Тогда какая разница, что я "дойду" до необходимости выделения 6 смежных ячеек, а что я их сразу запрошу? Свободных то все-равно нет. Это я имел ввиду в вопросе.
← →
Digitman © (2005-06-01 10:33) [22]
> Mx © (01.06.05 09:55) [14]
> Вроде ошибка: Out of memory, а не Unable to Realloc? Не
> хватает, как я понял, объема, причем здесь количество реалокейтов?
найти грабли достаточно легко - перехватываем точки входа в VirtualAlloc и LocalAlloc, протоколируем отказы этих вызовов
если отказал VirtualAlloc, грабли в дефрагментации и/или отсутствии региона нужного размера
если LocalAlloc, грабли - в исчерпании кучи
← →
Mx © (2005-06-01 10:38) [23]У меня примера нет. Может автор проверит?
← →
Digitman © (2005-06-01 10:41) [24]getmem.inc наглядно показывает grow-механизм работы ВММ - сначала в АП резервируется регион, а затем идет запрос к дифолтной куче
а в целом грабли даже и искать не нужно
лезем в sysutils.pas, видим там
EOutOfMemory = class(EHeapException);
т.е. отказ вернула ф-ция LocalAlloc из-за исчерпания кучи
← →
Mx © (2005-06-01 10:43) [25]Как на это влияет число вызовов ReallocMem?
← →
Digitman © (2005-06-01 11:00) [26]вот так и влияет
AddBlockAfter() вызывает LocalAlloc() посредством GetBlockDesc(), запрашивая из кучи блок памяти, а DeleteBlock() назад в кучу его не отдает
← →
Mx © (2005-06-01 11:32) [27]Все-равно не понимаю. Если я запрошу блок вначале 1*Integer, потом 2*Integer, потом 3*Integer. А если запрошу сразу 3*Integer, то что выиграю? И в том и в другом случае я запрашиваю 3*Integer ячеек. А! В первом случае получается 6*Integer, да? Правильно понял? Т.е., высвобождая предыдущий блок, ReallocMem не далает эти ячейки доступными для следующего Realloc"а?
← →
Digitman © (2005-06-01 11:58) [28]
> Mx © (01.06.05 11:32) [27]
> Если я запрошу блок вначале 1*Integer, потом 2*Integer,
> потом 3*Integer.
.. то потенциально возможны три вызова LocalAlloc() при ни одном LocalFree()
А если запрошу сразу 3*Integer, то что
> выиграю?
при этом будет не более чем один LocalAlloc()
ВММ оперирует понятием и механизмом "блок"
у каждого блока есть упр.структура для организации связного двунапр.списка из блоков, хранения атрибутов ("свободен", "занят"), адреса, где размещены собственно данные блока, если блок занят, и размера.
когда прикл.код обращается к ВММ за выделением памяти, ВММ ищет первый своб.блок минимально подходящего размера и помечает его как "занятый", манипулируя далее с соотв.регионами в АП процесса (1)
если своб.блок не найден, ВММ запрашивает из кучи фрагмент размера равного заголовку блока и затем выполняет (1)
когда прикл.код обращается к ВММ за освобождением памяти, ВММ ищет заголовок блока по полю размера, помечает этот блок как свободный, НЕ отдавая в кучу память, ранее запрошенную под этот блок, и далее опять же манипулирует соотв.регионами
реаллокация у ВММ - более сложный алгоритм (с т.з. манипуляций регионами), но сводится он опять же к аллокации нового и деаллокации прежнего блока
ну это так, сильно упрощенная схема ...
детали же реально происходящего - в getmem.inc
← →
Mx © (2005-06-01 12:35) [29]Как все запушено... Спасибо, на досуге повнимательнее почитаю getmem.inc.
← →
TrueCoder (2005-06-01 16:38) [30]Большое спасибо всем ответившим. Даже не ожидал, что моя проблема поднимет такую дискуссию. Есть о чем подумать.
Трезвую мысль высказал Digitman: "Cкорей всего АП процесса на момент отказа было сильно фрагментировано по причине предыдущих массированных мелких реаллокаций, инициированных вызовами SetLength()". Интуитивно чувствую, что истина где-то тут.
Код привести, к сожалению, не могу, запутанный он. Там долго и нудно обрабатываются Variant-ячейки таблицы ExpressQuantumGrid (штука классная, но сложнаяяяя..). Исключение возникает при обработке порции порядка 5 тысяч записей, но записи большие, в каждой по 50 полей, большинство текстовые, разной длины, поэтому я даже затруднюсь сказать максимальный размер массива.
Главное, что я понял - ошибка где-то в самом коде. Точнее, даже не в коде, а в самом принципе использования памяти - не любит ОНО многочисленных мелких реалокаций. Буду думать, может TList прикрутить получится.
Насторожило то, что исключение возникает при объеме памяти, занимаемого приложением, в 128 Мб. По неопытности и подумал, может по умолчанию при компиляции какие ограничения существуют. А нет, чуда не произошло. :)
← →
Digitman © (2005-06-01 17:05) [31]
> TrueCoder (01.06.05 16:38) [30]
> Трезвую мысль высказал Digitman
да ну нет)... на более пристальный взгляд она не такая уж и трезвая оказалась ..
EOutOfMemory = class(EHeapException);
т.е. отказ этот вызван исчерпанием хипа, а с хипом работает LocalAlloc, а LocalAlloc вызывается для аллокации памяти под заголовок блока, а не под сами данные, на которые занятый блок ссылается
отсюда и вывод напрашивается - с дефрагментацией АП еще бабушка надвое сказала, а вот хип опустошен тобой множеством реаллокаций по самое нехочу
> Буду думать, может TList прикрутить получится
ТЛист пользует тот же менеджер памяти, разница лишь в grow-алгоритме : ТЛист при добавлении эл-та списка запрашивает память сразу под несколько (не помню сколько - см. код класса) элементов, т.о. минимизируя число вновь создаваемых блоков и, соответственно, новых аллокациий в хипе
рано или поздно и с ТЛист можно нарваться на ту же ситуацию
> Насторожило то, что исключение возникает при объеме памяти,
> занимаемого приложением, в 128 Мб
а ты не гадай !
ты проверь или отвергни предположение насчет хипа ..
сразу после старта приложения запроси у системы хэндл дифолтного хипа своего процесса (GetProcessHeap) и тут же просканируй хип (HeapWalk)
а потом перед каждой своей итерацией, где ты реаллокируешь свой массив, делай тоже самое и следи за тем как изменяется состояние хипа
← →
Anatoly Podgoretsky © (2005-06-01 20:40) [32]Digitman © (01.06.05 17:05) [31]
ТЛист пользует тот же менеджер памяти, разница лишь в grow-алгоритме : ТЛист при добавлении эл-та списка запрашивает память сразу под несколько (не помню сколько - см. код класса) элементов, т.о. минимизируя число вновь создаваемых блоков и, соответственно, новых аллокациий в хипе
И не запомнишь, у него адаптирующий алгоритм, с переменным количеством сколько. Вроде бы каждое увеличение на 25% от текущего размера блока, чем больше запрашиваешь, чем больше получаешь.
← →
Defunct © (2005-06-02 01:21) [33]Anatoly Podgoretsky © (01.06.05 20:40) [32]
> разница лишь в grow-алгоритме
Не только. TList также был предложен по той причине, что размер непрерывного участка памяти при использовании TList будет минимально возможным, без переделывания логики программы.
← →
TrueCoder (2005-06-03 23:39) [34]Наверное, никому уже не интересно, но все же расскажу. Анализом кода удалось найти возможность заранее вычислять размер будущего массива, поэтому число реалокаций с 5 тысяч удалось снизить до двух. И проблема отпала сама собой.
Конечно, текущее решение значительно более красивое, как с точки зрения чистого кодинга, так и с точки зрения полученного удовлетворения от красивого решения проблемы :), но, все же, осталось некоторое удивление. Разве, стабильно работающая система (я о менеджере памяти Delphi и о самой операционке) не должны стабильно работать и при условии пусть некрасивых, но все же верных решений. Таких как 5 тысяч мелких реалокаций памяти у меня ранее. Что-то все же неладно в данном королевстве..
Интерес остался, конечно, исключительно чисто теоретический, проверять почему не работало раньше банально нет времени.
← →
Defunct © (2005-06-04 02:53) [35]> TrueCoder
Проблема фрагментации - вечная проблема. Менеджер памяти пойдет в ущер скорости исполения, если будет заниматься постоянной дефрагментацией.
Поэтому делайте всегда всё красиво.
Страницы: 1 вся ветка
Форум: "Основная";
Текущий архив: 2005.06.29;
Скачать: [xml.tar.bz2];
Память: 0.55 MB
Время: 0.134 c