вторник, 6 марта 2007 г.

Delphi 2007 (часть III - FastMM)

Введение

О чем речь

Ниже речь пойдет о FastMM - менеджере парамяти для Delphi for win32. Я поделюсь опытом использования данного продукта и покажу, какие можно получать выгоды от него.

Предполагается, что FastMM используется для отладки приложения. Это важно, т.к. FastMM, работающий в режиме отладки, серьезно тормозит выполнение программы.

Версии дельфи, для которых применим материал

Материал применим как для BDS2006, так и для Delphi2007 (возможно, что и для более ранных версий дельфи - я не проверял).

История FastMM

Насколько я понимаю, данный манагер памяти был алтернативным манагером до Delphi2005 включительно. Т.е. можно было скачать FastMM (он свободный, где брать - см. ниже) и включить его в свой проект, заменив штатный манагер. Начиная с BDS2006 FastMM уже включен в Delphi.

По сравнению с ранее существовавшим манагером FastMM обладает следующими достоинствами:

  1. Он быстрее.
  2. Он предоставляет сервисные функции для отладки и поиска утечек памяти.

Естественно FastMM включается не только в программы, создаваемый разработчиком, но и в саму IDE. Насколько я понимаю одной из причин резкого ускорения работы IDE при переходе от BDS2005 к BDS2006 был именно FastMM.

Проблемы

Проблема в том, что:

  1. В Delphi FastMM включается только как манагер памяти и как упрощенный поисковик утечек памяти.
  2. В Delphi в настоящий момент полностью отсутствует документация по поводу FastMM (есть соответствующий репорт - http://qc.codegear.com/wc/qcmain.aspx?d=27105, который уже открыт автором FastMM).

При этом никто не машает скачать полную версию FastMM и заменить ею "штатный" FastMM, который есть в Delphi, настроив заменяющий FastMM таким образом, чтобы получать максимальную выгоду от сервисных функций в процессе отладки.

Использование FastMM

Где брать

FastMM хостится на http://www.sourceforge.net. Вот точная ссылка на страницу проекта: http://sourceforge.net/projects/fastmm.

Далее идете по ссылке Download FastMM. и скачиваете версию 4.x. В настоящий момент последней версией является 4.78.

Как устанавливать

Устанавливать собственно и не надо ничего. Есть три файла, которые вам необходимы:

  1. FastMM4.pas
  2. FastMM4Messages.pas
  3. FastMM4Options.inc

Кладете их в каталог проекта (или в путях, прописанных в Library Path в настройках Delphi) и подключаете первым модулем проекта FastMM4.

В случае, если используются runtime пакеты, то FastMM4, естественно не нужно подключать в пакеты - только в главное приложение.

NB Как использовать FastMM для DLL я не знаю, ибо не разбирался. Но в файле FastMM4Options.inc есть очень много комментариев по поводу настройки FastMM. Скорее всего там можно выяснить этот вопрос.

Настройка FastMM

FastMM настраивается посредством опций условной компиляции, задаваемых в файле FastMM4Options.inc. Опции снабжены подробными комментариями. Я для своих задач сделал следующие изменения:

  1. Убрал комментарии для опций UseRuntimePackages, FullDebugMode, ClearLogFileOnStartup.
  2. Закомментировал опцию RequireDebuggerPresenceForLeakReporting.

NB При использовании FastMM с опцией FullDebugMode в каталоге проекта должна быть библиотека FastMM_FullDebugMode.dll, которая есть в поставке FastMM как в виде исходников, так и в виде скомпилированного файла. Подробнее см. каталог FastMM478.zip \FullDebugMode DLL.

Настройки проекта

При возникновении нештатных ситуаций FastMM показывает стек вызовов, приведший к ошибке. Для того, чтобы стек вызовов содержал имена функций и строк в исходном коде нужно в параметрах проекта (и runtime пакетов, если они используются) на закладке Linker в секции Map File выставить опцию Detailed.

Пример использования в целях получения стека вызовов, приведшего к утечке памяти

Цель примера показать, что в стеке вызовов, приведшего к утечке памяти будут методы TMyClass.Execute() и TMyClass.fProcess() с точным указанием номера строки исходного кода.

program Project1;
{$APPTYPE CONSOLE}
uses
FastMM4,Classes,SysUtils;
type
TMyClass = class
public procedure Execute();
strict private procedure fProcess();
end;
procedure TMyClass.Execute();
begin
fProcess();
end;
procedure TMyClass.fProcess();
var
L: TList;
begin
L := TList.Create();
end;
var
MC: TMyClass;
begin
MC := TMyClass.Create();
try
MC.Execute();
finally
MC.Free();
end;
end.

Еси выполнить данную программу, то, во-первых, при завершении программы будет выведено сообщение о том, что были утечки, во-вторых, в каталоге программы будет создан файл Project1_MemoryManager_EventLog.txt, в котором будет представлена более подробная информация об утечке памяти.

В частности будет представлена следующая информация:


Stack trace of when this block was allocated (return addresses):
402DBE [system.pas][System][System.@GetMem][2648]
4036EF [system.pas][System][System.TObject.NewInstance][8824]
40399A [system.pas][System][System.@ClassCreate][9489]
403724 [system.pas][System][System.TObject.Create][8839]
403A08 [system.pas][System][System.@AfterConstruction][9537]
414935 [E:\temp\_Borland\FieldTest\01\Project1.dpr][Project1][Project1.TMyClass.fProcess][18]
41491B [E:\temp\_Borland\FieldTest\01\Project1.dpr][Project1][Project1.TMyClass.Execute][12]
4152FD
794589A5 [ProcessIdToSessionId]
The block is currently used for an object of class: TList

Видно, что здесь, во-первых, указан класс, объект которого "утек", во-вторых, указан стек вызовов, приведший к утечке.

Можете попробовать использовать указанный подход в своих оконных приложениях (я не стал этого здесь делать т.к. код бы получился очень большой). В этом случае будет выводится очень глубокий стек, начиная от VCL.

NB В какой-то момент может показаться ошибкой, что в стек иногда попадают лишние методы. Я не силен в архитектуре возбуждения исключений, поэтому могу только передать слова автора FastMM: "Я пользовался функцией построения стека, которая реализована в JEDI". От себя замечу, что именно этой функцией пользуется сама IDE в BDS2006 и Delphi2007. Наверное, стоит спокойно относится к лишним методам - лишь бы нужные методы не пропадали.

Пример использования в целях получения стека вызовов, приведшего к вызову метода удаленного объекта

Такой функционал - еще одна сервисная функция, предоставляемая FastMM.

В отладочном режиме (т.е. если включена опция условной компиляции FullDebugMode) FastMM при удалении объекта "портит" память, занимаемую ранее объектом, таким образом, что при следующей попытке вызывать виртуальный метод удаленного объекта FastMM возбуждает исключение.

В заголовке текущего пункта я не указал, что речь идет про именно виртуальный метод. Я это не сделал сознательно т.к. подобная техника может быть использована и для обычных методов своих классов: можно временно сделать метод виртуальным и выяснить откуда же был вызван этот метод.

Ниже представлен пример отслеживания момента повторного удаления объекта (деструктор тоже виртуальный метод).

program Project1;
{$APPTYPE CONSOLE}
uses
FastMM4;
type
TMyClass = class
end;
var
MC: TMyClass;
procedure Proc1;
begin
MC := TMyClass.Create();
end;
procedure Proc2;
begin
MC.Free();
end;
procedure Proc3;
begin
MC.Free();
end;
begin
Proc1;
Proc2;
Proc3;
end.

Запустите проект.

Во-первых, вы получите сообщение о том, что была попытка вызывать виртуальный метод удаленного объекта.

Во-вторых, в файле Project1_MemoryManager_EventLog.txt можно посмотреть:

  1. Стек вызовов при создании объекта.
  2. Стек вызовов при удалении объекта.
  3. Стек вызовов при попытке вызывать виртуальный метод объекта (в текущем примере это тот же деструктор).

ИМХО весьма полезно может быть.

Заключение

При анализе файла FastMM4Options.inc можно найти некоторые другие сервисные функции. Я с ними не разбирался, но, очевидно, что они также могут быть полезны при отладке.

6 комментариев:

Анонимный комментирует...

FastMM отлично встает на D7 тоже, на ранних версиях не смотрел. Пользуюсь им уже год точно, впрочем, как и FastCode Library.

Странно, что FastCode Library (в режиме CpuID) не включили в состав BDS4. Ведь явное превосходство над RTL.

Единственное, что смущает, это QA. Если допустить "промах" на уровне FCL'a никакой debug не поможет. Совсем недавно обнаружился баг в одной из функций работы со строками.

С уважением.

Дмитрий Тимохов комментирует...

Я писал ранее, что что-то они все-таки включили. Посмотри. Я там даже свои тесты производительности делал.

Вообще у них идея бинарной совместимости новой версии с bds2006. Поэтому они просто не могут очень много включать. Я надеюсь, что они будут это делать в будущем.

Анонимный комментирует...

Я понял что включили некоторые функции (из секции работы со строками), просто в FCL очень много реализаций (в том числе из Math.pas), которыми "народ" обычно пользуется.

P.S. Радует новая версия FastMM 4.78

Дмитрий Тимохов комментирует...

Да, именно так - что-то из FastCode они включили.

Что именно можно посмотреть в блоге http://hallvards.blogspot.com/2007/03/review-delphi-2007-for-win32-beta-part_06.html в секции "Optimized RTL routines"

Анонимный комментирует...

конечно старая статья , на дворе 2009 год :) но всетаки
может подскажете почему FatsMM не выдает мне в логе номера строк в файле исходнике
пишет тока
This block was allocated by thread 0x538, and the stack trace (return addresses) at the time was:
402963 [System][@GetMem]
43558C [Controls][TWinControl.WndProc]
4356C4 [Controls][DoControlMsg]
43558C [Controls][TWinControl.WndProc]
435207 [Controls][TWinControl.MainWndProc]
41E376 [Classes][StdWndProc]
77D38709 [Unknown function at GetDC]
77D387EB [Unknown function at GetDC]
77D3B743 [Unknown function at GetParent]
77D3B7AB [SendMessageW]
77D6FC9D [Unknown function at CreateMDIWindowA]

Блок в настоящее время используется для объекта класса: Unknown


не указывая ни размер ни строку
Ж(

Дмитрий Тимохов комментирует...

Чтобы номер строки был надо указать детальны *.MAP файл, чтобы компилятор делал. Это вроде в настройках проекта на закладки linker делаецо.