-
Доброго! Поставил программу на ночь, утром Error out of memory. Диспетчер показывает 1900 мб выделенной виртуальной памяти (Virtual size). Откуда? Кто знает - поделитесь! Есть дерево:
PNode=^TNode;
TNode=packed record
left,right,top:PNode;
text:string;
cnt:integer;
end;
После запуска process explorer в разделе "Virtual memory" показывает 21'632 кб private bytes и 75'144 virtual size. После выполнения кода for i:=1 to 4000000 do New(PNode).text:='1234567890'; цифры подскакивают соответственно до 209'520 кб private bytes и 254'344 virtual size. Соответственно выделено 187888 и 179200 кб. Даже если брать меньшее значение, делить его на 4 млн итераций, получится 45 байт на одну record. Считаем: left,right,top:PNode = 4б+4б+4б = 12б text:string = контент(10б)+длина(4б)+счётчик ссылок(4б)+нуль-терминатор(1б) = 19б cnt:integer = 4б Итого только 35 байт. Откуда взялись ещё 10? Что за накладные расходы???
-
Да, забыл, раньше запись была просто record, без packed. Я думал - поможет. Не помогло))
Если смотреть по колонке Physical memory, то выходит аж 48 байт на каждую запись.
Кстати, обнаружился ещё прикол - память под строки выделяется с выравниванием по 4 байта - т.е. под вышеописанную структуру, где в строке 8,9,10 или 11 символов, даётся 48 байт, 12,13,14 или 15 - 52 байта. Не знал.
-
SizeOf(TNode) = 20. Это натолкнуло на мысль, что s - ведь тоже указатель, поэтому плюсуем 4 байта. Итого 39. Плюс 1 байт на выравнивание строки. 40. Откуда ещё пять? Или даже восемь, если смотреть по колонке Physical memory?
-
> память под строки выделяется с выравниванием по 4 байта
с чеготы взял, ТОЛЬКО под строки? Плюс под каждое выделение памяти скорее всего еще 4 байта менеджер памяти забирает, чтоб знать сколько памяти освобождать при вызове FreeMem
> SizeOf(TNode) = 20. Это натолкнуло на мысль, что s - ведь > тоже указатель, поэтому плюсуем 4 байта. Итого 39.
И так вертел, и сяк, так и не понял, как 20 + 4 стало равно 39?
-
Только под строки, потому что запись PACKED. А кроме записи больше ничего нет))) 20 - это размер записи (left,right,top,cnt и text дают по 4 байта каждый). Плюс размер данных внутри строки - 10б. Плюс длина строки - 4б. Плюс счётчик ссылок на строку - 4б. Плюс нуль-терминатор, который даже в String на всякий случай есть - 1б. С выравниванием (поскольку строка НЕ packed) - ещё 1б.
Итого 40. Ещё 8 потерялись непонятно куда...
-
ну так служебная информация о выделенном блоке памяти тоже где-то должна храниться.
-
Насчёт ещё 4-х байт при каждом выделении памяти... Сомневаюсь. Это же не GetMem/FreeMem, а New/Dispose, совсем другой механизм. И по-моему, размер выделенной ранее памяти определяется при вызове dispose ещё на этапе компиляции, на основе типа указателя. то есть: dispose(pointer(p)) - освободит ХЗ чего dispose(PNode(p)) - освободит строку и запись.
-
RWolf Так вот мне и интересно - что за служебная информация такая??
-
Окей, вот ещё одну "дырку" нашёл:
> Менеджер памяти Delphi заблаговременно резервирует необходимую > память блоками по 1М и выделяет её программе блоками по > 16К. Причем блоки всегда выровнены по 4 байта. Так же в > блоке присутствует заголовок — двойное слово в котором хранится > информация о размере этого блока и его статусная информация. >
Делим 187 888 выделенных килобайт на 16, получем 11 734 блока по 16*1024=16 384 байт. Размер записи 40б, поэтому в блоке умещается 409 записей общим объёмом 40*409 = 16360 байт и остаются неиспользоваными 384-360 = 24 байта. Плюс служебный заголовок - 8 байт, итого 32. Умножаем на число блоков: 11 734*32 = 375 488 байт или 336,6 кб пропадает зря. Вычитаем их из общей суммы: 187888-336 = 187552 кб. И это всё равно те же самые 48 с копейками байт на одну запись. Копейки, видимо, из-за ещё каких-то неучтённых глобальных распределений.
-
Для получения достоверной информации достаточно протрассировать процесс получения-освобождения памяти.
P.S. В свежих версиях Дельфи тонкости внутренней работы могут отличаться от семерки, поскольку менеджер памяти изменился, но на принципах работы на высоком уровне это не скажется.
-
> Плюс размер данных внутри строки - 10б. > Плюс длина строки - 4б. > Плюс счётчик ссылок на строку - 4б. > Плюс нуль-терминатор, который даже в String на всякий случай > есть - 1б. > С выравниванием (поскольку строка НЕ packed) - ещё 1б.
всё это хорошо, за исключение одного момента, у тебя всего одна строка, которая шарится между всеми записами. > Это же не GetMem/FreeMem, а New/Dispose, совсем другой механизм. >
тот же самый: function _New(size: Longint; typeInfo: Pointer): Pointer;
begin
GetMem(Result, size);
if Result <> nil then
_Initialize(Result, typeInfo);
end;
asm
PUSH EDX
CALL _GetMem
POP EDX
TEST EAX,EAX
JE @@exit
PUSH EAX
CALL _Initialize
POP EAX
@@exit:
end;
procedure _Dispose(p: Pointer; typeInfo: Pointer);
begin
_Finalize(p, typeinfo);
FreeMem(p);
end;
-
> всё это хорошо, за исключение одного момента, у тебя всего > одна строка, которая шарится между всеми записами.
Прикольно :) Может, так и должно быть, но у в моём примере это совсем не так. Как говорится, теоретически, между теорией и практикой не должно быть различий, но на практике... ... При увеличении длины строки растёт и объём памяти. Каждые дополнительные 4 символа к той строке, из примера, - это плюс (4 * 4 000 000) байт выделенной памяти.
Предполагаю, что шарятся строки только при присвоении string:=string, а здесь string:=const. Но только предположение.
А вот это function _New(...) - откуда??? В system.pas вроде нету. И почему два параметра....?
ЗЫ Делфи как раз седьмая... ... ...
-
MBo Протрассировал. На каждой итерации GetMem вызывается дважды. Первый раз из самого new, при этом в eax лежит 14h или 20d Второй раз из NewAnsiString, в eax опять лежит 14h или 20d
Итого сорок! *wall* Где ещё 8 ?
-
Ну, правда, проблему это не решило. Я ещё раз прогнал программу - эти лишние 8 байтиков не при чём, она валится уже на двустах тысячах узлов в дереве. При этом если просто строить дерево, всё хорошо! Процедура добавления/удаления отлажена и вылизана. А вот если строить дерево в реальной процедуре, то начинает дико, бешенно расти размер виртуальной памяти!!! В 10-30 раз быстрее, чем реальной. Ну, то есть, реальной выделено, скажем, 150 мб, виртуальной - 1900 мб (предел). И если закомментировать строчку добавления в дерево, а остальное оставить, тоже всё хорошо. Как такое может быть?
-
На всякий случай, выкладываю проект - может глянете кто-нибудь? Вдруг сразу будет видно где неправильно? Вырезал всё лишнее, оставил только что касается проблемы. Хелп! http://www.cyberforum.ru/attachment.php?attachmentid=167358&d=1341398734Чтобы смоделировать баг, в окно программы нужно перетащить несколько файлов с русским текстом (книги, например). Желательно побольше, штук 100, можно одинаковых - не суть. Чтобы было хотя бы 100 мб текста в общей сложности. После этого будет видно как угрожающе растёт VirtualMemory во время анализа... Кстати, если подключить FastMM, баг не проявляется так заметно. Но становятся заметны утечки - за 3 минуты работы утекает около 9 мб вирт. и 3 мб. реальной памяти. Откуда там утечки - я ума не приложу. Да ещё в таком количестве! В общем, нужна помощь экспертов!
-
>Где ещё 8 ? GetMem(20) на самом деле выделяет 24 байта памяти: перед блоком - его размер, jack128 уже это отмечал
-
Ааа... Тогда всё сходится! А проект не посмотрите? Куда так много вирт. памяти девается??
-
слишком много кода, чтобы все отследить глазами. AddWordNode вроде ничего предосудительного не делает. А вот DelTree - сомнительная - количество вызовов dispose совпадает с количеством new?
-
Да, я проверял, всегда совпадает.
Ещё обнаружилось, что если закомментировать блок "Сохранение", память выделяется нормально. Странно...
|