Тест производительности программы в Delphi XE3

Автор: Alex. Опубликовано в Программирование . просмотров: 3849

Рейтинг:  5 / 5

Звезда активнаЗвезда активнаЗвезда активнаЗвезда активнаЗвезда активна
 

При разработке приложений часто возникает потребность оптимизировать код таким образом, чтобы он работа быстрее. Чтобы это сделать, зачастую мало внимательно изучить код. Ведь медленно работать может сторонняя библиотека или компонент. Да и программный код может быть очень объёмным. Давайте разберёмся, как можно проанализировать производительность программы в Delphi XE3.

AQtime

Для начала посмотрим, есть ли для Delphi что-нибудь аналогичное мастеру производительности в Visual Studio. Такое средство есть, - это инструментальный профайлер AQtime от компании SmartBear. В таком виде профайлера, в вашу программу внедряются вызовы функций профайлера, которые и занимаются сбором статистики.

К сожалению, по умолчанию с Delphi поставляется урезанная версия инструмента. Разницу между урезанной и полной версиями можно посмотреть на странице сравнения этих версий. За лицензию на полную версию придётся выложить 599 (на один компьютер) или 1899 (за плавающую лицензию) долларов.

Бесплатной версией этого профайлера, по моему мнению, пользоваться невозможно, т.к. подсчёт времени выполнения кода делается для функций и процедур целиком. Для каждой строчки кода подсчёт времени не ведётся. А ведь задержки могут быть в любой строке.

Тем не менее, посмотрим, как это работает. После установки AQtime на компьютер (вместе с дистрибутивом Delphi XE3) в папке с документами будет создана папка AQtime 7 Samples с примерами. Найдите и откройте пример из папки AQtime 7 Samples\Unmanaged\Performance\Delphi\Cycles.dpr. В примере есть форма с кнопкой Execute, которая запускает выполнение цикла в процедуре ProfilingTest.

procedure ProfilingTest (Param : Integer);
    var i : Integer;
begin
    DoActionA(); { call the procedure that performs action A }
    DoActionB(); { call the procedure that performs action B }
    for i:=1 to Param do
        DoActionC(); { call the procedure that performs action C }
    MessageBox(MainForm.Handle, 'Execution finished.', 'Performance Sample', MB_OK);
end;

Перед запуском удостоверьтесь, что выбран профайлер производительности: должна стоять галочка «Performance Profiler».

Перед запуском удостоверьтесь, что выбран профайлер производительности: должна стоять галочка «Performance Profiler»

Также включите отладочную информацию (TD32) в исполняемый файл. Для этого в свойствах проекта в категории Delphi Compiler | Compiling установите галочки напротив опций Debug information, Local symbols и Use debug .dcus, а оптимизацию кода отключите, это галочка Optimization.

Включите отладочную информацию TD32 в исполняемый файл

В категории Delphi Compiler | Linking установите галочку Debug information.

В категории Delphi Compiler | Linking установите галочку Debug information

Полностью все рекомендации по подготовке проекта к тестированию для всех версий Delphi, смотрите на официальной странице поддержки.

Теперь, чтобы запустить программу, воспользуйтесь пунктом меню AQtime->Run with profiling, в диалоге «Run settings» оставьте всё по умолчанию и нажмите кнопку «Run».

На моём компьютере сразу появилась надпись, что процессор поддерживает технологию SpeedStep и результаты могут быть неточными. Если вы оптимизируете математические вычисления или что-то аналогичное, то лучше отключить динамическое изменение частоты вашего процессора. Но для примера это совсем не важно, поэтому нажимаем «Yes».

Предупреждение о том, что процессор поддерживает технологию SpeedStep

Дальше мы увидим окно с предупреждением, что в функции Finalization и initialization не может быть внедрён код для измерения времени. В правой колонке указаны причины. Функция Finalization в данном случае меньше 5 байт, а в функции initialization нет инструкции ret. Не будем здесь обращать внимание на предупреждения, и просто нажмём «OK».

Дальше мы увидим окно с предупреждением, что в функции Finalization и initialization не может быть внедрён код для измерения времени

В запустившейся программе, нажмите на кнопку «Execute» и после того как программа отработает, закройте окно программы. При этом сразу откроется закладка «Report» с отчётом об измерениях.

Закладка «Report» с отчётом об измерениях

В левой колонке содержатся имена функций и процедур, в колонке Time – время выполнения, в колонке Time with Children – время выполнения вместе с временем выполнения дочерних функций и процедур, Shared Time – процент от общего времени, Hit Count – количество вызовов. Двойной щелчок по строке в этой таблице отправит нас к нужной функции. По колонке Time сразу видно, что дольше всего работает процедура DoActionB, значит, её и нужно оптимизировать.

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

Отчёт об измерениях в коде программы

Как я уже писал, в платной версии вы получите такой же расчёт для каждой строки кода. Ниже картинка (с официального сайта) с изображением результата теста программы с помощью платной версии AQtime для C++, но для Delphi анализ кода будет точно таким же.

Результата теста программы с помощью платной версии AQtime для C++

SamplingProfiler

SamplingProfiler - это бесплатный неинструментальный профайлер, который вы можете скачать со странички разработчика. Работает он очень хитро, через определённые промежутки времени профайлер проверяет стек вашего исполняемого приложения. Проект не нужно изменять, чтобы измерить время работы тех или иных функций и строк кода. В этом есть и плюсы и минусы. Плюс такого подхода состоит в том, что при измерении ваша программа работает как обычно в режиме отладки, без особых задержек, и вы получаете более точные измерения, как если бы программа выполнялась без профайлера. К минусам можно отнести меньшее количество собираемой информации.

Этот профайлер не смог точно позиционироваться на нужные строки кода в исходниках, поэтому я не буду подробно на нём останавливаться. Может быть, он плохо работает только с XE3, а на остальных версиях работает превосходно? Не знаю.

Этот профайлер существует с 2005 года. Сейчас профайлер поддерживает все версии Delphi до XE4.

Чтобы профайлер работал нужно так же, как это было описано выше для профайлера AQtime, включить в проект отладочную информацию TD32. Когда проект настроен и скомпилирован можно приступать к тестированию. Запустите профайлер, укажите путь к исполняемому файлу (поле «Application Name»), выберите пути к исходным файлам Delphi (в поле «Standard Source Files Search Paths» выберите свою версию Delphi или выберите значение «Use custom paths only» и перечислите пути самостоятельно в поле «Custom Source Files Search Paths»), в поле «Custom Source Files Search Paths» добавьте пути к исходным файлам проекта, если в проект многопоточный то в поле «Samples Gathering Mode» установите значение «Monte-Carlo Samples Gathering».

Настройка профайлера SamplingProfiler

После того как всё настроено, запустите свою программу нажав на кнопку с зелёным треугольником. При этом ваша программа запустится. После того как программа отработает результат автоматически появится на новой закладке. Как видно из картинки, строка кода, которую я хочу посмотреть 1759-я, а у меня в примере всего 53 строки. Именно поэтому я не буду дальше рассматривать этот профайлер. Но может быть, кому то повезёт больше, и профайлер SamplingProfiler будет работать корректно.

Результат работы профайлера SamplingProfiler

Напоследок скажу ещё, что проект и результаты тестирования можно сохранить в файлы. Причём результаты тестирования можно объединять.

GpProfile

GpProfile – это очень старый инструментальный профайлер с открытыми исходниками и полностью бесплатный. Если вы захотите скомпилировать исходники, то сделать это можно только с помощью Delphi 2007. На всех последующих версиях Delphi этот проект не соберётся. Тем не менее, исполняемый файл gpprof.exe будет работать со всеми версиями от Delphi 2 до Delphi XE3. Официальная страница проекта находится здесь. Отсюда же можно скачать юниты, которые нужно подключать к вашему проекту, исполняемый файл gpprof.exe и файл справки.

Чтобы сбор статистики был возможен, профайлер GpProfile меняет ваши исходники, поэтому обязательно делайте бэкап ваших исходных файлов! Итак, посмотрим, как работает этот профайлер. Я сделал тестовый проект, запустил программу gpprof.exe и в окне профайлера выбрал файл своего проекта.

Откройте файл проекта в GpProfile

Проект открылся и в списке юнитов появился мой единственный юнит Unit1. После выбора юнита, в соседнем списке открылся класс TForm1, щёлкнув на который открылся список процедур. Щелчок на процедуру отображает исходный код в текстовом редакторе снизу. Теперь ставим галочки на нужных процедурах, в моём случае, - это процедура Button1Click.

Вставка кода профайлера GpProfile в исходники

После этого можно нажать выбрать пункт меню «Project -> Instrument and Run» или «Project -> Instrument». Первый пункт меню внедряет инструменты в код проекта и запускает Delphi XE3, второй только внедряет инструменты. Выберем второй из перечисленных пункт меню. После нажатия на него, в начало функции Button1Click была добавлена следующая строчка кода:

{>>GpProfile} ProfilerEnterProc(1); try {GpProfile>>}

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

{>>GpProfile} finally ProfilerExitProc(1); end; {GpProfile>>}

Также в секцию uses был добавлен следующий код:

{>>GpProfile U} GpProf, {GpProfile U>>}

Дальше в проект нужно добавить файлы GpProf.pas и gpprofh.pas и можно компилировать и запускать приложение. После того как приложение отработает в окне профайлера на закладке «Analysis» появятся результаты измерений.

Результаты работы профайлера GpProfile

Как видите, здесь подсчитывается время выполнения в процентах и секундах и количество вызовов. Но всё это, к сожалению, делается для функций в целом. Здесь нет сбора статистики для каждой строки кода.

Чтобы убрать из вашего проекта куски кода профайлера GpProfile, установите галочки напротив всех процедур и выберите пункт меню «Project -> Remove Instrumentation». Затем удалите файлы GpProf.pas и gpprofh.pas из проекта.

TStopwatch

И конечно всегда остаётся вариант – замерять время самостоятельно. Я для этого сделал класс TStopwatch (секундомер). По сути – это тоже инструментальный профайлер. У него всего 4 статические функции: Start – для запуска секундомера, Stop – для остановки, Clear – для очистки результатов и Show – для вывода результатов в консоль отладчика EventLog (пункт меню View -> Debug Windows -> Event Log). Секундомер TStopwatch умеет параллельно делать сразу много измерений. Для этого в функцию Start и Stop передаётся тэг измерения. Т.е. можно измерять одновременно время работы процедур, строк или блоков кода.

Для измерения вам нужно добавить в проект юнит Stopwatch.pas и расставить в нужных местах вызовы функций Start и Stop класса TStopwatch и в конце измерения вызвать функцию Show (при завершении работы приложения и функция Show также вызывается автоматически).

Для примера, создадим проект с формой, добавим на форму кнопку. При клике на кнопку измерим работу цикла, в котором будем создавать список уникальных чисел сгенерированных случайным образом. Заодно проверим скорость работы функций Add и Contains у классов TList<T> и TDictionary<T>.

uses System.Generics.Collections, Stopwatch;
 
procedure TForm1.Button1Click(Sender: TObject);
var
   i: integer;
   v: string;
   list: TList<string>;
   dictionary: TDictionary<string, string>;
   contains1, contains2: boolean;
begin
 
   //Очищаем результаты предыдущих измерений
   TStopwatch.Clear;
 
   //Начало работы процедуры
   TStopwatch.Start('Процедура TForm1.Button1Click(Sender: TObject)');
 
   //Инициализация переменных
   TStopwatch.Start('Инициализация переменных');
   contains1 := false;
   contains2 := false;
   list := TList<string>.Create;
   dictionary := TDictionary<string, string>.Create;
   TStopwatch.Stop('Инициализация переменных'); 
 
   //Добавляем в список и словарь 50000 уникальных чисел
   TStopwatch.Start('Цикл заполнения списка и словаря');
   for i := 1 to 50000 do
   begin
      repeat
 
         //Число для добавления в список и словарь
         v := IntToStr(Random(2147483647));
 
         //Поиск элемента в списке
         TStopwatch.Start('Функция list.Contains(v)');
         contains1 := list.Contains(v);
         TStopwatch.Stop('Функция list.Contains(v)');
 
         //Поиск элемента в словаре
         TStopwatch.Start('Функция dictionary.ContainsKey(v)');
         contains2 := dictionary.ContainsKey(v);
         TStopwatch.Stop('Функция dictionary.ContainsKey(v)');
 
      //Проверка есть ли число в словаре и списке
      until (not contains1) and (not contains2);
 
      //Добавление элемента в список
      TStopwatch.Start('Функция list.Add(v)');
      list.Add(v);
      TStopwatch.Stop('Функция list.Add(v)');
 
      //Добавление элемента в словарь
      TStopwatch.Start('Функция dictionary.Add(v, v)');
      dictionary.Add(v, v);
      TStopwatch.Stop('Функция dictionary.Add(v, v)');
 
   end;
   TStopwatch.Stop('Цикл заполнения списка и словаря');
 
   //Удаление объектов
   TStopwatch.Start('Удаление объектов');
   list.Free;
   dictionary.Free;
   TStopwatch.Stop('Удаление объектов');
 
   //Процедура закончила работу
   TStopwatch.Stop('Процедура TForm1.Button1Click(Sender: TObject)');
 
   //Выводим результаты измерения в EventLog
   TStopwatch.Show;
 
end;

После запуска посмотрим результаты:

Результат работы секундомера TStopwatch

Из результатов видно, что львиная доля всего времени потрачена на вызов функции Contains объекта TList<T>. А функция Contains объекта TDictionary<T> отработала примерно в 250 раз быстрее. Также из результатов видно, что функция Add объекта TDictionary<T> работает в 2 раза медленнее той же функции объекта TList<T>.

Пара слов о многопоточности. Работать с секундомером TStopwatch можно из разных потоков, но нужно учитывать, что функции Start и Stop для одного тэга должны вызываться, строго по очереди, начиная с функции Start и заканчивая функцией Stop. Чтобы не заниматься синхронизацией потоков вы можете добавлять в начало тэгов идентификатор потока:

TStopwatch.Start(IntToStr(GetCurrentThreadId) + ': Вызов из потока');
//
//Измеряемый код здесь
//
TStopwatch.Stop(IntToStr(GetCurrentThreadId) + ': Вызов из потока');

Юнит Stopwatch.pas с классом TStopwatch вы можете скачать здесь:

Файлы:
TStopwatch Версия:от 01.09.2014

Секундомер TStopwatch к статье "Тест производительности программы в Delphi XE3".

Дата 02.09.2014 Размер файла 3.41 KB Закачек 581

Подведём итог всему вышесказанному. Конечно, выбирать инструмент для тестирования вашей программы вы будете самостоятельно, но всегда нужно знать какие инструменты есть ещё. И не обязательно тестировать с помощью одного профайлера, можно тестировать сразу несколькими. Возможно, это даст лучший результат.

Tags: TStopwatch GpProfile SamplingProfiler SmartBear AQTime Delphi Учебники по программированию Учебники по использованию программ Обзоры инструментов для программирования

Добавить комментарий


Защитный код
Обновить