Конференция "WinAPI" » New(PNode) выделяет слишком много памяти... [D7, WinXP]
 
  • Postscripter © (04.07.12 01:54) [0]
    Доброго! Поставил программу на ночь, утром 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? Что за накладные расходы???
  • Postscripter © (04.07.12 02:19) [1]
    Да, забыл, раньше запись была просто record, без packed. Я думал - поможет. Не помогло))

    Если смотреть по колонке Physical memory, то выходит аж 48 байт на каждую запись.

    Кстати, обнаружился ещё прикол - память под строки выделяется с выравниванием по 4 байта - т.е. под вышеописанную структуру, где в строке 8,9,10 или 11 символов, даётся 48 байт, 12,13,14 или 15 - 52 байта. Не знал.
  • Postscripter © (04.07.12 02:27) [2]
    SizeOf(TNode) = 20. Это натолкнуло на мысль, что s - ведь тоже указатель, поэтому плюсуем 4 байта. Итого 39. Плюс 1 байт на выравнивание строки. 40. Откуда ещё пять? Или даже восемь, если смотреть по колонке Physical memory?
  • jack128_ (04.07.12 09:21) [3]

    > память под строки выделяется с выравниванием по 4 байта

    с чеготы взял, ТОЛЬКО под строки? Плюс под каждое выделение памяти скорее всего еще 4 байта менеджер памяти забирает, чтоб знать сколько памяти освобождать при вызове FreeMem


    > SizeOf(TNode) = 20. Это натолкнуло на мысль, что s - ведь
    > тоже указатель, поэтому плюсуем 4 байта. Итого 39.

    И так вертел, и сяк, так и не понял, как 20 + 4  стало равно 39?
  • Postscripter © (04.07.12 10:56) [4]
    Только под строки, потому что запись PACKED. А кроме записи больше ничего нет)))
    20 - это размер записи (left,right,top,cnt и text дают по 4 байта каждый).
    Плюс размер данных внутри строки - 10б.
    Плюс длина строки - 4б.
    Плюс счётчик ссылок на строку - 4б.
    Плюс нуль-терминатор, который даже в String на всякий случай есть - 1б.
    С выравниванием (поскольку строка НЕ packed) - ещё 1б.

    Итого 40. Ещё 8 потерялись непонятно куда...
  • RWolf © (04.07.12 11:05) [5]
    ну так служебная информация о выделенном блоке памяти тоже где-то должна храниться.
  • Postscripter © (04.07.12 11:14) [6]
    Насчёт ещё 4-х байт при каждом выделении памяти... Сомневаюсь. Это же не GetMem/FreeMem, а New/Dispose, совсем другой механизм. И по-моему, размер выделенной ранее памяти определяется при вызове dispose ещё на этапе компиляции, на основе типа указателя. то есть:
    dispose(pointer(p)) - освободит ХЗ чего
    dispose(PNode(p)) - освободит строку и запись.
  • Postscripter © (04.07.12 11:16) [7]
    RWolf Так вот мне и интересно - что за служебная информация такая??
  • Postscripter © (04.07.12 11:39) [8]
    Окей, вот ещё одну "дырку" нашёл:


    > Менеджер памяти 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 с копейками байт на одну запись. Копейки, видимо, из-за ещё каких-то неучтённых глобальных распределений.
  • MBo © (04.07.12 13:01) [9]
    Для получения достоверной информации достаточно протрассировать процесс получения-освобождения памяти.

    P.S. В свежих версиях Дельфи тонкости внутренней работы могут отличаться от семерки, поскольку менеджер памяти изменился, но на принципах работы на высоком уровне это не скажется.
  • jack128_ (04.07.12 13:08) [10]

    > Плюс размер данных внутри строки - 10б.
    > Плюс длина строки - 4б.
    > Плюс счётчик ссылок на строку - 4б.
    > Плюс нуль-терминатор, который даже в String на всякий случай
    > есть - 1б.
    > С выравниванием (поскольку строка НЕ packed) - ещё 1б.

    всё это хорошо, за исключение одного момента, у тебя всего одна строка, которая шарится между всеми записами.

    > Это же не GetMem/FreeMem, а New/Dispose, совсем другой механизм.
    >  

    тот же самый:

    function _New(size: Longint; typeInfo: Pointer): Pointer;
    {$IFDEF PUREPASCAL}
    begin
     GetMem(Result, size);
     if Result <> nil then
       _Initialize(Result, typeInfo);
    end;
    {$ELSE}
    asm
           { ->    EAX size of object to allocate  }
           {       EDX pointer to typeInfo         }

           PUSH    EDX
           CALL    _GetMem
           POP     EDX
           TEST    EAX,EAX
           JE      @@exit
           PUSH    EAX
           CALL    _Initialize
           POP     EAX
    @@exit:
    end;
    {$ENDIF}

    procedure _Dispose(p: Pointer; typeInfo: Pointer);
    {$IFDEF PUREPASCAL}
    begin
     _Finalize(p, typeinfo);
     FreeMem(p);
    end;

  • Postscripter © (04.07.12 16:50) [11]

    > всё это хорошо, за исключение одного момента, у тебя всего
    > одна строка, которая шарится между всеми записами.


    Прикольно :) Может, так и должно быть, но у в моём примере это совсем не так. Как говорится, теоретически, между теорией и практикой не должно быть различий, но на практике... ... При увеличении длины строки растёт и объём памяти. Каждые дополнительные 4 символа к той строке, из примера, - это плюс (4 * 4 000 000) байт выделенной памяти.

    Предполагаю, что шарятся строки только при присвоении string:=string, а здесь string:=const. Но только предположение.

    А вот это function _New(...) - откуда??? В system.pas вроде нету. И почему два параметра....?

    ЗЫ Делфи как раз седьмая... ... ...
  • Postscripter © (04.07.12 17:08) [12]
    MBo Протрассировал. На каждой итерации GetMem вызывается дважды.
    Первый раз из самого new, при этом в eax лежит 14h или 20d
    Второй раз из NewAnsiString, в eax опять лежит 14h или 20d

    Итого сорок! *wall* Где ещё 8 ?
  • Postscripter © (04.07.12 17:19) [13]
    Ну, правда, проблему это не решило. Я ещё раз прогнал программу - эти лишние 8 байтиков не при чём, она валится уже на двустах тысячах узлов в дереве. При этом если просто строить дерево, всё хорошо! Процедура добавления/удаления отлажена и вылизана. А вот если строить дерево в реальной процедуре, то начинает дико, бешенно расти размер виртуальной памяти!!!  В 10-30 раз быстрее, чем реальной. Ну, то есть, реальной выделено, скажем, 150 мб, виртуальной - 1900 мб (предел). И если закомментировать строчку добавления в дерево, а остальное оставить, тоже всё хорошо. Как такое может быть?
  • Postscripter © (04.07.12 17:29) [14]
    На всякий случай, выкладываю проект - может глянете кто-нибудь? Вдруг сразу будет видно где неправильно? Вырезал всё лишнее, оставил только что касается проблемы. Хелп!

    http://www.cyberforum.ru/attachment.php?attachmentid=167358&d=1341398734

    Чтобы смоделировать баг, в окно программы нужно перетащить несколько файлов с русским текстом (книги, например). Желательно побольше, штук 100, можно одинаковых - не суть. Чтобы было хотя бы 100 мб текста в общей сложности. После этого будет видно как угрожающе растёт VirtualMemory во время анализа...

    Кстати, если подключить FastMM, баг не проявляется так заметно. Но становятся заметны утечки - за 3 минуты работы утекает около 9 мб вирт. и 3 мб. реальной памяти. Откуда там утечки - я ума не приложу. Да ещё в таком количестве!

    В общем, нужна помощь экспертов!
  • MBo © (04.07.12 18:19) [15]
    >Где ещё 8 ?
    GetMem(20) на самом деле выделяет 24 байта памяти: перед блоком - его размер, jack128 уже это отмечал
  • Postscripter © (04.07.12 18:31) [16]
    Ааа... Тогда всё сходится! А проект не посмотрите? Куда так много вирт. памяти девается??
  • MBo © (04.07.12 21:06) [17]
    слишком много кода, чтобы все отследить глазами.
    AddWordNode вроде ничего предосудительного не делает. А вот DelTree - сомнительная - количество вызовов dispose совпадает с количеством new?
  • Postscripter © (05.07.12 03:42) [18]
    Да, я проверял, всегда совпадает.

    Ещё обнаружилось, что если закомментировать блок "Сохранение", память выделяется нормально. Странно...
 
Конференция "WinAPI" » New(PNode) выделяет слишком много памяти... [D7, WinXP]
Есть новые Нет новых   [134430   +4][b:0][p:0.002]