![]() |
| ||
|
|
Новости |
Поиск |
FAQ |
Статьи |
Книги |
Новости VCL
| | Форумы ЧАТ |
Орешник |
|
| |||
| |||
« предыдущая ветвь | форум | следующая ветвь »
Out of Memory - непонятки
Franzy © (2008-11-17 14:14:00)Пишу прогу, в которой приходится работать с большими массивами данных. Прога загружает файл STL (примитивный формат СAD, фактически - список треугольников в формате: список кооржинат узлов плюс нормаль), совершает некие операции последовательно с каждым треугольником, затем записывает результат операций в другой файл.
С тестовыми (небольшими) файлами работает. С большими - в определенный момент вылетает с ошибкой Out Of Memory. Проблема в том, что, по моим расчетам, памяти должно хватать, причем с лихвой. При счете открываю Диспетчер задач, вижу по графику, что жрет 100 мегов там, где должно хватать одного мега. Т.е. имеет место memory leak, который я не могу определить.
Объектов никаких не использую, зато использую динамические массивы (а также динамические массивы динамических массивов). Возможно, я как-то не так освобождаю память?
Вот характерный пример используемых мною структур данных:
Type
TBaseContour = record
length : longint; //кол-во узлов
node : array of longint; //список узлов базового контура
End;
TBaseFace = record
contour : array of TBaseContour; //список баз. контуров грани
nContours : longint; //Кол-во контуров в грани
facet : array of longint; //список номеров треугольников, образующих грань
nfacets: longint; //кол-во тр-ков
End;
TBaseVolume = record
face : array of TBaseFace; //грани, образующие объект
nFaces : longint; //кол-во граней
end;
Var
volume : array of TBaseVolume;
nVolumes : longint; // number of volumes
Заранее длины дин. массивов не известны, поэтому в процессе счета их длины меняются при необходимости с помощью SetLength.
Это глобальные структуры, которые заявляются один раз (в них записываются результаты). Плюс я при анализе каждого треугольника использую дополнительные "одноразовые" массивы (тоже динамические). Чтобы освободить память после использования такого массива я пишу: [имя массива]:=nil;
Но складывается ощущение, что память при этом не освобождается, т.е. Windows вместо повторного использования зарезервированных ранее сегментов памяти резервирует новые...
Может, я неправильно освобождаю память? Или проблема в структурах, включающих в себя динамические массивы?
(2009-04-01 12:05:00) [1]Удалено модератором
Amoeba (2008-11-20 15:44:00) [2]
> novex (19.11.08 21:30) [27]
>
>
> > Чтобы освободить память после использования такого массива
> > я пишу: [имя массива]:=nil
В случае динамических массивов (и длинных строк) компилятор самостоятельно добавляет код выполняющий освобождение выделенной памяти.
http://www.delphikingdom.com/asp/viewitem.asp?catalogid=1184
Anatoly Podgoretsky (2008-11-19 23:53:00) [3]> novex (19.11.2008 21:30:27) [27]
Какое еще Free - это же не объект
sniknik (2008-11-19 23:04:00) [4]> Судя по комментариям в FastMM - он именно так и работает:
> ...
ну, вот этого я как раз и не знал, думал он идет "от обратного" и наоборот собирает в кучу всю бездумно зафрагментированную программистом память. но я честно предупреждал что не знаю как он на самом деле работает...
> Во всяком случае, так я понял статью:
все правильно, но почему ты это назвал ошибками и "выделением большого кол-ва маленьких блоков", реально то наоборот, запрашивается один большой, который и распределяется после.
Германн (2008-11-19 21:56:00) [5]
> меня в школе учили что выделенную память всегда нужно освобождать
> самому и не надеятся на сборшики мусора, неужели что-то
> изменилось?
>
Да. Появились динамические массивы. :)
novex (2008-11-19 21:30:00) [6]
> Чтобы освободить память после использования такого массива
> я пишу: [имя массива]:=nil;
я чето невкурю, а разве больше не надо делать free? а потом := nil?
меня в школе учили что выделенную память всегда нужно освобождать самому и не надеятся на сборшики мусора, неужели что-то изменилось?
Sapersky (2008-11-19 21:07:00) [7]аналогично, но я вижу дурное в подобном
for i:= 1 to 1000000 do begin
SetLength(mas, i);
... код использующий добавку к массиву на еденицу...
end;
А если там будет TList.Add? Конечно, лучше заранее выставить Capacity - но допустим, что оно неизвестно.
А если алгоритм перевыделения наподобие TList реализован непосредственно в менеджере? Судя по комментариям в FastMM - он именно так и работает:
{This pointer is being reallocated to a larger block and therefore it is
logical to assume that it may be enlarged again. Since reallocations are
expensive, there is a minimum upsize percentage to avoid unnecessary
future move operations.}
{Must grow with at least 100% + x bytes}
LNewAllocSize := LOldAvailableSize * 2 + SmallBlockUpsizeAdder;
Для "средних" блоков перевыделение делается экономнее:
{Couldn't upsize in place. Grab a new block and move the data across:
If we have to reallocate and move medium blocks, we grow by at
least 25%}
LMinimumUpsize := LOldAvailableSize + (LOldAvailableSize shr 2);
Для "больших" тоже 25%.
> ведь он тоже "замазывает" кое-какие ошибки - выделение большого кол-ва маленьких блоков, например.
бред. даже не смешно.
Ну то есть выделение большого кол-ва маленьких блоков через дельфийский менеджер будет быстрее, чем через системный, т.к. дельфийский запрашивает у системы сразу много памяти (минимизируя кол-во системных вызовов) и затем отдаёт программе по кусочкам.
Во всяком случае, так я понял статью:
http://www.rsdn.ru/article/Delphi/memmanager.xml
...хотя читал, честно говоря, "по диагонали", в каких-то деталях могу ошибаться.
Anatoly Podgoretsky (2008-11-19 19:33:00) [8]
> SetProcessWorkingSetSize не поможет
Она и не может помочь, поскольку работает с виртуальной памятью, а дефрагментация с физической.
> Позор! Нормальный программист должен всё делать вручную!
> :)
Должен уметь делать вручную!, а дальше в силу вступает правило - должен, но не обязан.
sniknik (2008-11-19 19:21:00) [9]> Фрагментация, ИМХО, возникает на уровне дельфийского менеджера памяти, а не системного, так что SetProcessWorkingSetSize не поможет.
истинно так, именно в дельфийском, но функция требует от программы освободить неиспользуемую память, что без дефрагментации (поджимания используемых блоков начало, и отделения неиспользуемого "конца") невозможно.
спорить не буду, с самого начала сказал что не уверен, и надо проверить, без этого все переливание из пустого в порожнее.
> И в FastMM эта функция не используется.
а кто такое сказал? плюнь ему в глаза, или в зеркало. и вообше читай написаное, не выдумывай.
было сказано "эта функция сделает примерно тоже самое", а не "там используется вот это ..."
> Ничего дурного в решении проблемы с использованием FastMM не вижу.
аналогично, но я вижу дурное в подобном
for i:= 1 to 1000000 do begin
SetLength(mas, i);
... код использующий добавку к массиву на еденицу...
end;
цикл это чисто для наглядности, это может быть не цикл а например таймер, событие и т.д., сам принцип приращения по одному.
операция перераспределения памяти довольно "тяжеловесная".
> ведь он тоже "замазывает" кое-какие ошибки - выделение большого кол-ва маленьких блоков, например.
бред. даже не смешно.
Sapersky (2008-11-19 15:50:00) [10]Фрагментация, ИМХО, возникает на уровне дельфийского менеджера памяти, а не системного, так что SetProcessWorkingSetSize не поможет. И в FastMM эта функция не используется.
Ничего дурного в решении проблемы с использованием FastMM не вижу. Почему бы не переложить рутину на "волшебный" модуль? По сути, насколько я смог разлядеть, он автоматически делает то, что пришлось бы делать вручную - выделяет память "с запасом". Да, вручную, возможно, получилось бы эффективнее - но в большинстве случаев "овчинка выделки не стоит".
Опять же, по вашей логике следует отказаться и от стандартного дельфийского менеджера... ведь он тоже "замазывает" кое-какие ошибки - выделение большого кол-ва маленьких блоков, например.
Позор! Нормальный программист должен всё делать вручную! :)
sniknik (2008-11-19 11:26:00) [11]никто и не говорил что "там только это", но это именно та часть которая прячет твои ошибки по работе с памятью.
трудно что ли проверить? рем на обьявление модуля FastMM4 поставить и в цикл себе вставить указанную функцию?
слишком частый вызов скорее всего приведет к тормозам, но проблему нехватки памяти уберет, и если уберет... ну. это повод задуматься. чего ты до сих пор кажется избегаешь. нашёл себе "волшебный" модуль, списал свои глюки на неправильную работу менеджера дельфи и тихо радуешься, думать не надо...
Franzy (2008-11-19 11:08:00) [12]Размер pas-файла FastMM - 340кб. Вряд ли там только это :) Она полностью заменяет менеджер памяти Дельфей.
sniknik (2008-11-17 16:54:00) [13]> Скачал FastMM4, скомпилировал с ним... Библиотека творит чудеса! Больше память никуда не теряется. Спасибо!!!
она и без неё никуда не теряется, в прямом смысле, в переносном же она потеряна для работы т.к. ты её зафрагментировал неправильним использованием по самое не могу...
т.е. чудо только в том, видимо, что она временами проводит "сборку мусора" объединяет неиспользуемые куски в большие блоки. (фактически "замазывает" твои ошибки, ведь неправильная работа никуда не делась... просто после кто то исправляет последствия)
насчёт сборки мусора в FastMM4, это конечно имхо, догадки, на самом деле не знаю как она реально работает.
но можно проверить. вот эта функция сделает примерно тоже самое (подожмёт используемую память, уберет фрагментированные блоки, и отдаст "лишнее" системе)
SetProcessWorkingSetSize(GetCurrentProcess, dword(-1), dword(-1));
убери FastMM4, и сам время от времени вызывай эту функцию. результат должен быть аналогичным (если я прав конечно).
это я к чему, библиотеки это дело хорошее (хорошие библиотеки)... но и самому надо писать правильно. (типа, на Гейтца надейся а сам не плошай. :))
Franzy (2008-11-17 16:03:00) [14]2Sapersky
Скачал FastMM4, скомпилировал с ним... Библиотека творит чудеса! Больше память никуда не теряется. Спасибо!!!
Franzy (2008-11-17 15:40:00) [15]По идее, конечно, массив должен на месте остаться. Но черт знает этот менеджер... Потому и спрашиваю :)
Anatoly Podgoretsky (2008-11-17 15:37:00) [16]> sniknik (17.11.2008 15:23:16) [16]
Конечно редко, поскольку трудно обеспечить, что ни будь уже захватило следующий байт вот и придется копировать.
Я кстати предложил сделать простой тест, в цикле ситуация расширения весьма вероятна. Во всяком случае покажет как это работает.
sniknik (2008-11-17 15:23:00) [17]> А если длина массива уменьшается, он как, остается на месте или тоже перезаписывается целиком в новое место?
а сам то ты как думаешь? представь что ты разрабатываешь свой менеджер памяти
вот тебе 4 массива в памяти, как ты увеличишь первый на единицу? желательно затратив меньше ресурсов/времени, так чтобы не было "такой проблемы"?
111222233333444444
есть вообще возможность оставить его на месте? и второй вопрос, есть ли возможность оставить его на месте при уменьшении?
имхо, это очевидно. просто чуть логики.
> Растягивание тоже делается, если блок крайний.
логично, но имхо, это бывает крайне редко...
Sapersky (2008-11-17 15:23:00) [18]А если длина массива уменьшается, он как, остается на месте или тоже перезаписывается целиком в новое место?
По идее должен оставаться, хотя зависит от конкретной реализации менеджера памяти.
Однозначно перезаписывается - если RefCount > 1, т.е. на данный блок ссылаются две и более переменных-массива (было присвоение arr2 := arr1).
Anatoly Podgoretsky (2008-11-17 15:17:00) [19]> Franzy (17.11.2008 15:09:11) [11]
Сделай тесты, наращивай массив в цикле выводи значение адреса, тоже сделай и в реальной программе.
Anatoly Podgoretsky (2008-11-17 15:15:00) [20]> Franzy (17.11.2008 15:04:10) [10]
Диспетчер памяти всегда компромис, между скорость и ресурсами.