Глава 1. Пример работы с профилировщиком

Профилирование — это одна из еще пока недооцениваемых, но очень полезных и даже жизненно необходимых частей процесса создания качественного программного обеспечения. Обзоры показывают, что, на самом деле, только незначительная часть профессиональных программистов использует профилировщики для улучшения своих программ. Другие же исследования говорят о том, что в большинстве случаев даже опытные программисты неправильно указывают критические участки («узкие места») в своих программах.


Каковы же преимущества использования описываемого нами орудия? Во-первых профилирование Ваших программ может повысить их производительность. Во-вторых профилирование может усилить вашу способность создания эффективных программ. И, наконец, профилирование, как и отладка, может стать неотъемлемым элементом цикла создания программного обеспечения.


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


* обнаруживать где и на что ваша программа расходует время;


* создавать аннотированный листинг исходного текста Вашей программы и статистический отчет о профилировании;


* запоминать статистические данные, полученные в результате профилирования и затем, после перерыва, возвращаться к работе с этими данными;


* анализировать статистику профилирования и исходный текст Вашей программы, находящиеся, в соседних окнах.


Примечание: Примеры этой главы основаны на колонке Джона Бентли в журнале Communications of the ACM (июль

1987), которая называется «Жемчужины программирования»

(«Programming Pearls»).


Примечание: Все примеры были опробованы на машине с 286 процессором и видеоадаптером Hercules.


Примеры в данной главе основываются на поиске и распечатке всех простых чисел, находящихся в диапазоне от 1 до 1000. Вспомним, что число является простым если оно делится только на 1 и само на себя, очевидно, что оно должно быть нечетным, так как любое четное число делится на 2 и, следовательно, не является простым. Для того чтобы определить, является ли данное число простым, необходимо проверить не является ли какое либо простое число, меньшее данного, его делителем, а так же проверить не делится ли рассматриваемое число на какое — нибудь целое, большее двух первых простых чисел 2 и 3.


Целью профилирования программ-примеров является ускорение процесса нахождения и печати простых чисел. Проработав эти примеры Вы научитесь использовать Turbo Profiler для определения эффективности структуры программы каждого из примеров.


Первой программой, которую мы рассмотрим, будет PRIME0. Как только Вы отпрофилируете ее и поймете в каких местах необходимо изменить ее текст, Вам необходимо переходить к следующему шагу — загрузке и профилированию PRIME1. За исключением PRIME1, каждая из программ, рассматриваемых в данной главе (PRIME2, PRIME3, PRIME4, PRIME5 и PRIME6) является модификацией предыдущей.


Важное замечание: Информация для пользователей Паскаля.

Программы PRIMEn.* написаны на Turbo C. Для пользователей Паскаля имеются аналоги этих программ PRIMEnPA.* которые могут использоваться для упражнений во время чтения данной главы. Каждое обсуждение профилирования программы, написанной на С сопровождается комментарием, отражающим отличия в случае профилирования Паскалевской программы.


Убедитесь, что все файлы с примерами (PRIMEn.C и PRIMEn.EXE или PRIMEnPA.PAS и PRIMEnPA.EXE) находятся в Вашей текущей директории.


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


Для того чтобы обеспечить наличие полной символической информации для Ваших программ, необходимо при их компиляции задать следующие параметры:


Turbo C++: При работе в интегрированной среде, необходимо выбрать в меню Options| Full Menus (Параметры| Полный набор меню) затем открыть блок диалога отладчика (для этого надо выбрать Options| Debugger (Параметры| Отладчик)), и установить Source Debugging (Отладка на уровне исходного кода) в значение Standalone (Самостоятельный отладчик). При компиляции с использованием командной строки необходимо задавать параметр — v.


Turbo C: Если Вы компилируете в интегрированной среде, то необходимо перед компиляцией программы выбрать Standalone (Самостоятельный отладчик) в параметрах отладчика Debug| Source Debugging (Отладчик| Отладка на уровне исходного текста). При компиляции с использованием командной строки необходимо задавать параметр — v.


Turbo Pascal: Если Вы компилируете в интегрированной среде, необходимо установить пункты меню Options|Debug Information (Параметры|Отладочная информация) и Debug|Stand-Alone Debugging (Отладка| Самостоятельный отладчик) в состояние On. При компиляции с помощью командной строки необходимо использовать параметр /v.


Turbo Assembler: Необходимо использовать параметр /zi, затем строить загрузочный модуль при помощи TLINK, задавая при этом параметр /v.


Информация для пользователей 80x87.


Поскольку Turbo Profiler способен использовать возможности математического сопроцессора, в том случае если сопроцессор установлен на Вашем компьютере, и Вы хотите получить статистические данные, соответствующие изложенным в настоящем руководстве, то Вам необходимо временно запретить использование сопроцессора (в противном случае полученные вами результаты будут значительно отличаться от приведенных в этом тексте). Для того чтобы Turbo Profiler не пользовался возможностями сопроцессора достаточно перед началом работы с профилировщиком выполнить команду DOS SET 87=N. Но даже в этом случае полученные статистические данные могут сильно отличаться от изложенных в данном руководстве, из-за несовпадения технических характеристик компьютеров таких, например, как быстродействие процессора.


Профилирование программы (PRIME0)


Процесс профилирования и корректировки программы состоит из следующих четырех этапов:


1. Загрузка программы перед ее профилированием.


2. Сбор данных во время выполнения программы.


3. Анализ полученных данных.


4. Модификация программы и ее повторная компиляция.


После внесения изменений в программу необходимо повторить выполнение пунктов с 1 по 3 для того, чтобы посмотреть как повлияли эти изменения на поведение программы в процессе исполнения.


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


Загрузите PRIME0 в систему Turbo Profiler, набрав на клавиатуре Turbo Profiler PRIME0 и нажав клавишу ENTER.


Примечание: Вы можете закончить работу с профилировщиком в любой момент. Для этого необходимо выбрать в меню последовательность File|Quit (Файл|Выход) или нажать комбинацию Alt-X.


Важное замечание: Информация для пользователей Паскаля.

Если Вы хотите профилировать Паскалевскую версию PRIME0.C, то убедитесь, что файлы PRIME0PA.PAS и PRIME0PA.EXE находятся в Вашей текущей директории после чего наберите на клавиатуре TPROF PRIME0PA и нажмите Enter.


В начале работы Профилировщика откроются два окна: Module (Модуль) (в котором находится исходный текст PRIME0) и Execution Profile (Профиль выполнения) (в котором, после того как Вы запустите программу PRIME0, будет отображена статистика процесса ее выполнения).


Рис. 1.1 Turbo Profiler с загруженной в него программой PRIME0.


Окно Module (Модуль) и окно Execution Profile (Профиль выполнения) относятся к шагам 1 и 3 процесса профилирования. Окно Module (Модуль) используется для того, чтобы помечать участки программы, которые необходимо профилировать. После того как Вы запустите программу, в окне Execution Profile (Профиль выполнения) отображается информация, необходимая для анализа ее работы.


Установка параметров профилирования.


Перед началом профилирования программы необходимо пометить в ней интересующие Вас «области». «Область» — это участок программы для которого Вы хотели бы получить статистические данные. «Областью» может быть одна строка, один оператор (например оператор цикла) или вся программа целиком. Во время первого сеанса профилирования Вам потребуется собрать более обширную информацию о «областях» чем та, которая задается параметрами системы Turbo Profiler по умолчанию.


Для того, чтобы проанализировать небольшое количество коротких подпрограмм (таких, как prime и main в данной программе), Вы должны знать сколько раз выполняется каждая их строка и сколько времени занимает ее выполнение. Для того, чтобы получить эту информацию, Вы должны пометить каждую строку этой программы как «область».


1. Нажмите Alt-F10, для того чтобы активировать локальное меню окна Module (Модуль).


2. Выберите в этом меню Add Areas (Добавить «области»). Вам будет предложен для выбора список возможных границ «области».


3. Выберите Every Line in Module (Каждая строка в модуле). В результате этого маркеры «области» будут установлены для каждой строки программы и курсор вернется в окно Module (Модуль).


Обратите внимание на то, что в результате выполнения перечисленных операций все выполнимые строки программы оказались помеченными маркером =>.


Сбор статистических данных.


Итак, в настоящий момент Вы готовы к проведению второго этапа профилирования. Нажмите клавишу F9 для запуска PRIME0 под контролем Turbo Profiler. Программа выдаст на экран пользователя простые числа, находящиеся в диапазоне от 1 до 1000. Когда выполнение программы завершится, просмотрите информацию, появившуюся в окне Execution Profile (Профиль выполнения). Это и есть статистика процесса выполнения Вашей программы.


Распахните окно Execution Profile (Профиль выполнения), нажав клавишу F5 или выбрав Zoom (Распахнуть) из меню Window (Окно). После чего окно Execution Profile (Профиль выполнения) будет выглядеть следующим образом:


Рис. 1.2 Статистика процесса выполнения программы PRIME0.


Верхняя панель окна Execution Profile (Профиль выполнения) показывает общее время выполнения программы, сопровождаемое информацией о данных, находящихся на нижней панели. В каждой строке нижней панели содержится четыре позиции:


* название «области»;


* количество секунд, затраченных на выполнение данной области;


* процентное отношение этого времени к общему времени выполнения программы;


* горизонтальная диаграмма, пропорциональная этому отношению.


Строка вида


#PRIME0#31 6.2655 sec 93 % |============================ говорит о том, что тридцать первая строка программы PRIME0 выполнялась в течение 6.3 секунды, что составило 93 % от времени выполнения всех помеченных «областей». Диаграмма для строки 31 автоматически занимает все отведенное для диаграммы место, так как на выполнение 31 строки затрачивается самое большое количество времени среди всех помеченных областей.


Важное замечание: Информация для пользователей Паскаля. В программе PRIME0PA, соответствующая строка имеет номер 42.


Изображение статистики.


Также Вы можете просмотреть количественные данные о процессе выполнения Вашей программы.


1. Нажмите Alt-F10 для вызова меню окна Execution Profile (Профиль выполнения).


2. Выберите в этом меню команду Display (Изображение)


Блок диалога для установки параметров изображения предоставляет пять способов изображения данных в окне Execution Profile (Профиль выполнения).


Рис. 1.3 Блок диалога задания параметров изображения


* Time («Время») задает изображение полного времени затраченного на выполнение каждой из помеченных «областей». (этот параметр задается по умолчанию)


* Count («Подсчет») задает изображение числа, показывающего сколько раз за время выполнения программы управление передавалось помеченной «области».


* Both («И то, и другое») задает одновременный показ времени выполнения и количества вызовов.


* «Per Call» («На один вызов») ведет к изображению среднего времени затраченного на одно выполнение помеченной «области».


* Longest («Самое большое») ведет к показу максимального времени затраченного на выполнение данной «области».


3. Выберите Counts (Подсчет) в графе Display (Изображение) в рассматриваемом блоке диалога. (Сделайте это при помощи мыши, или используя клавиши управления курсором перейдите к этому пункту и нажмите клавишу ENTER, того же самого результата можно достичь простым нажатием клавиши С.)


4. Выберите ОК (или нажмите ENTER).


Теперь окно Execution Profile (Профиль выполнения) показывает не временную, а количественную статистику выполнения программы PRIME0, как показано на следующем рисунке.


Рис. 1.4 Количественная статистика в окне профиля программы:.


Эти изображенные на экране данные говорят о том, что наиболее часто в программе PRIME0 выполняется строка 22. (Соответствующая строка в программе PRIME0PA имеет номер 31.)


При желании Вы можете посмотреть количественную и временную статистику одновременно. Для этого нужно снова вызвать блок диалога Display Options (Параметры изображения) (нажав для этого Alt-F10 и выбрав Display (Изображение) или нажав Ctrl-D).


Выберите Both («И то, и другое») в столбце Display (Изображение), затем выберите ОК или нажмите ENTER. (Для того, чтобы это сделать надо выбрать Both («И то, и другое») при помощи мыши, или перейти к Both («И то, и другое») при помощи клавиш управления курсором и нажать ENTER, также можно просто нажать клавишу В).


Теперь окно Execution Profile (Профиль выполнения) выглядит следующим образом:


Рис. 1.5 Временная и количественная статистика в окне Execution Profile (Профиль выполнения).


Если в окне Execution Profile (Профиль выполнения) временная и количественная статистика изображаются одновременно, то для каждой помеченной «области» первое число, относящееся к этой «области» — это количество выполнений данной «области», а второе число — это время, затраченное на выполнение данной «области». Рисунок 1.5 показывает, что «область» PRIME0#22 (строка 22 в программе PRIME0) выполнялась 15 122 раза, что заняло в общей сложности 0.31 секунды.


Печать текстов программ и статистики.


В этом разделе Вы научитесь печатать две вещи:


1. Листинг исходного текста профилируемой программы, находящейся в окне Module (Модуль) с количественной и временной статистикой, сопровождающей каждую помеченную «область».


2. Статистику профилирования, изображенную в окне Execution Profile (Профиль выполнения).


Печать листинга профилируемой программы.


Перед тем как вывести в файл листинг профилируемой программы, содержащий временную и количественную статистику, Вы должны задать соответствующие параметры печати:


1. Выберите команду Print|Options (Печать|Параметры).


2. В блоке диалога Printing Options (Параметры печати) выберите селективную кнопку File (Файл) для этого нажимайте TAB до тех пор пока не активируются селективные кнопки, затем, при помощи клавиши «стрелка вниз», выберите параметр File (Файл)).


3. При помощи клавиши TAB активируйте блок ввода Destination File (Файл назначения) и наберите на клавиатуре PRIME0SC.LST


4. Выберите ASCII для того чтобы пользоваться стандартной кодировкой ASCII, а не расширенным набором символов IBM.


5. Выберите ОК или нажмите (ENTER).


Теперь курсор находится в активном окне Execution Profile (Профиль выполнения).


Для того, чтобы вывести листинг в файл выберите в меню команду Print|Module (Напечатать|Модуль). В блоке диалога PIck a Module (Выбрать модуль) при помощи клавиши «стрелка вниз» выберите название модуля PRIME0, затем нажмите ENTER (или выберите ОК).


Вы можете выйти в DOS (не выходя из системы Turbo Profiler), чтобы просмотреть файл PRIME0SC.LST, который должен быть выведен в текущую директорию. Выберите в меню команду File|Dos shell (Файл|Оболочка DOS) и наберите на клавиатуре


TYPE PRIME0SC.LST.


Если Вы профилируете программу PRIME0, написанную на Turbo C, Вы должны увидеть на экране следующее:


/* Copyright (c) 1990, Borland International */

/* Программа генерации простых чисел при помощи алгоритма Эвклида */


int primes[1000];

#define MAXPRIMES 1000


main()

{

int j;

int lastprime, curprime;


primes[0] = 2;

primes[1] = 3;

lastprime = 1;

curprime = 3;


printf(«prime %d = %d\n», 0, primes[0]);

printf(«prime %d = %d\n», 1, primes[1]);

while(curprime < MAXPRIMES)

{

for(j = 0; j <= lastprime; j++)

if((curprime % primes[j]) == 0)

{

curprime += 2;


break;

}

if(j <= lastprime)

continue;

lastprime++;

printf(«prime %d = %d\n», lastprime, curprime);

primes[lastprime] = curprime;

curprime += 2;

}

}


Примечание: времена выполнения «областей», фигурирующие в Вашем файле могут отличаться от приведенных здесь из-за различий между Вашим компьютером и тем, на котором были получены изображенные результаты.


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


Теперь наберите на клавиатуре EXIT и нажмите клавишу ENTER для того, чтобы вернуться в среду системы Turbo Profiler.


Отчет о статистике профилирования.


При необходимости Вы можете вывести содержимое окна Execution Profile (Профиль выполнения) в файл или на принтер.


1. Снова выберите Print|Options (Принтер|Параметры).


2. Выберите селективную кнопку Printer (Принтер).


3. Выберите Graphics (Графика) для того, чтобы включить в выдачу псевдографические символы IBM (Если ваш принтер не поддерживает псевдографические символы, пропустите этот шаг и переходите к шагу 4.)


4. Нажмите ENTER или (выберите ОК).


5. Выберите Print|Statistics (Печать|Статистика).


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


Сохранение и восстановление статистических данных.


Перед тем как продолжить сеанс профилирования, научимся записывать в файл статистические данные о процессе выполнения программы PRIME0, для того, чтобы иметь возможность в любой момент покинуть среду системы Turbo Profiler, не потеряв при этом необходимые данные. Также мы научим Вас считывать эти данные в среду профилировщика при последующих сеансах работы.


Выберите команду Statistics| Save (Статистика| Сохранить) для сохранения статистики профилирования в файле с расширением TFS (Turbo Profiler Statistics (Статистические данные системы Turbo Profiler)). Так как в окне Module (Модуль) находится PRIME0, то блок ввода File Name (Имя файла) по умолчанию предлагает PRIME0.TFS для названия файла в котором будет хранится статистика. Для того, чтобы создать этот файл выберите ОК.


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

Для того, чтобы просмотреть статистику, загрузите PRIME0 в профилировщик и выберите в меню команду Statistics|Restore (Статистика|Восстановить). Как и в предыдущий раз, в блоке ввода File Name (Имя файла) по умолчанию будет задан файл PRIME0.TFS. Нажмите ENTER для перехода в блок списка Files (Файлы), выберите в нем PRIME0.TFS и, затем, выберите ОК для восстановления статистических данных из этого файла.


Анализ статистики.


В этом разделе Вы научитесь анализировать статистику, находящуюся в окне Execution Profile (Профиль выполнения), для того, чтобы на основании этой статистики вносить в вашу программу улучшения.


Для начала еще раз посмотрим на временную и количественную статистику, находящуюся в окне Execution Profile (Профиль выполнения). Распахните окно Execution Profile (Профиль выполнения (Выберите команду Zoom (Распахнуть) из меню Window (Окно) или нажмите F5) и посмотрите на статистику для строк 22 и 31 (операторы if и printf).


Важное замечание: Пользователям Паскаля. В PRIME0PA им соответствуют строки 31 (if) и 42 (Writeln).


Из полученного профиля мы можем почерпнуть много полезных сведений. Например, легко заметить, что строка 22 в PRIME0 выполняется гораздо чаще чем любой другой оператор. Вполне понятно, что строка 22 выполняется 15 122 раза, так как в этой строке происходит проверка каждого числа в диапазоне от 4 до 1000 на делимость этого числа на элементы массива primes, проверка происходит до тех пор пока либо после деления остаток станет равным 0, либо массив primes будет исчерпан. Так же можно видеть, что строка 31 (оператор printf) отнимает большую часть времени выполнения программы.


Примечание: в программе PRIME5 содержится модифицированный вариант оператора printf, (пользователям Паскаля предназначена программа PRIME5PA с оператором CRT).


Одновременный просмотр исходного текста и статистики.


Данные в окне Execution Profile (Профиль выполнения) показывают, что в строке 22 программа выполняет большую работу, чем нам необходимо. Но полную картину того, что происходит во время выполнения программы можно получить лишь при одновременном рассмотрении временной и количественной статистики с исходным текстом программы.


Вам необходимо сопоставить временные и количественные данные, находящиеся в окне Execution Profile (Профиль выполнения) с соответствующими строками исходного текста программы, находящегося в окне Module (Модуль).


Вот один из способов одновременного получения на экране дисплея исходного текста и статистики профилирования:


1. Изменим размеры и положение окна Execution Profile (Профиль выполнения) так, чтобы оно заняло правую половину вашего экрана. Для этого нужно выбрать в меню команду Window|Size/Move (Окно|Размеры/Положение) или нажать Ctrl-F5.


2. Следуйте указаниям, находящимся в строке состояния для того, чтобы:


а. Изменить размеры окна так, чтобы оно занимало весь экран в высоту и половину экрана в ширину.


в. Передвинуть полученное окно вправо.


После того как шаги а и в будут проделаны, нажмите клавишу ENTER.


3. Активируйте окно Module (Модуль), нажав для этого клавишу F6, затем измените его размеры и расположение так, чтобы оно заняло левую половину экрана.


4. Снова вернитесь в окно Execution Profile (Профиль выполнения) (нажав для этого клавишу F6).


Мышь: Изменение размеров окна с помощью мыши происходит посредством перемещения по экрану маркера изменения размера, находящегося в нижнем правом углу.

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


Между окном Execution Profile (Профиль выполнения) и окном Module (Модуль) автоматически поддерживается связь, поэтому когда Вы перемещаетесь по исходному тексту программы, содержимое окна Execution Profile (Профиль выполнения) все время соответствует тому участку программы на котором Вы находитесь в данный момент.

Для того, чтобы посмотреть этот механизм в действии необходимо сделать следующее:


1. Активируйте окно Execution Profile (Профиль выполнения) (нажав для этого F6) и переместите световой маркер на первую строку (статистика для строки 31 программы PRIME0, или строки 42 программы PRIME0PA).


2. Откройте локальное меню (нажмите для этого Alt-F10) и выберите команду Module (Модуль) (или просто нажмите Ctrl-M).


Профилировщик установит курсор в окне Module (Модуль) на строке 31.


3. При помощи клавиш управления курсором переместитесь на строку 22 исходного текста программы (строка 31 в PRIME0PA).


Эта строка занимает второе место по затратам времени в программе PRIME0. Теперь в двух верхних строках окна Execution Profile (Профиль выполнения) находятся данные профилирования для рассматриваемого оператора if.


4. Переместите курсор в окне Module (Модуль) на строку 21 (на строку 29 в PRIME0PA) и посмотрите как изменилось содержимое окна Execution Profile (Профиль выполнения). Верхние строки в окне Execution Profile (Профиль выполнения) теперь содержат статистику для 21 строки программы.


5. Переместите курсор на строку 30 (на строку 42 в PRIME0PA) и посмотрите на данные в окне Execution Profile (Профиль выполнения).


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


Сохранение конфигурации окон.


На данном этапе настал подходящий момент для того, чтобы запомнить изменения, произведенные в конфигурации системы Turbo Profiler. Если Вы не сохраните текущее расположение и размеры окон, то при следующем сеансе работы с профилировщиком окна опять примут положение и размеры, задаваемые по умолчанию.


1. Выберите команду Options|Save Options (Параметры|Сохранить параметры). На экране появится блок диалога Save Configuration (Сохранение конфигурации).


2. По умолчанию триггерные кнопки Options (Параметры), находящиеся в данном блоке диалога уже установлены, и все установленные параметры (такие, например, как параметры изображения окна Execution Profile (Профиль выполнения)) записываются в файл конфигурации.


3. В блоке диалога Save Configuration (Сохранение конфигурации) при помощи клавиши TAB переместитесь в Layout (Компоновка) и нажмите клавишу «пробел». В результате этого текущая компоновка окон «стык в стык» будет сохранена в файле конфигурации.


4. По умолчанию конфигурация записывается в файл TFCONFIG.TF, его имя уже установлено в блоке ввода Save To (Сохранить в).

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


Как только Вы запускаете систему Turbo Profiler, она ищет файл TFCONFIG.TF, по умолчанию считается, что файл конфигурации имеет это имя. Как только профилировщик находит этот файл, он автоматически принимает конфигурацию, задаваемую параметрами, сохраненными в этом файле.


Измерение эффективности работы «области» программы.


Отношение общего времени, затраченного на работу данной «области» программы к числу передач управления на данную «область» является хорошей мерой для вычисления эффективности работы как одного оператора, так и всей программы.


Для того, чтобы посмотреть это отношение для «областей», определенных в PRIME0, измените параметр изображения для окна Execution Profile (Профиль выполнения). Это можно сделать следующим образом:


1. Из локального меню окна Execution Profile (Профиль выполнения) выберите команду Display (Изображение). (Для этого необходимо нажать комбинацию клавиш Alt-F10.)


2. В появившемся блоке диалога выберите значение Per Call (На один вызов) параметра Display (Изображение).


3. Выберите ОК (или нажмите ENTER).


Теперь Вы можете видеть, что строка 22 работает гораздо эффективнее строки 31 (в PRIME0PA строки 30 и 41 соответственно).

Несмотря на то, что общее время, затраченное на выполнение данной строки, велико, поскольку эта строка выполняется большое количество раз, среднее время ее выполнения составляет менее одной миллисекунды. Заметим, что среднее время выполнения строки 31 составляет приблизительно 38 миллисекунд (в PRIME0PA среднее время выполнения строки 42 равно 28 миллисекундам).


Данные, полученные профилировщиком показывают пути уменьшения времени работы PRIME0 и упрощения ее структуры. Можно выделить две стратегии улучшения программы:


1. Сокращение времени, затрачиваемого на выполнение операций ввода/вывода.


2. Изменение структуры операторов цикла на более эффективную и рациональную.


Проблема ввода/вывода может быть частично решена за счет сокращения оператора printf с его настоящего вида


printf(«prime #%d=%d\n», lastprime, curprime)


до вида


printf(«%d\n», curprime).


Информация для пользователей Паскаля: Вы можете привести оператор Writeln к виду Writeln(CurPrime);.


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

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

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


К счастью существует более оптимальный способ поиска простых чисел, но для того, чтобы им воспользоваться необходимо полностью изменить алгоритм, что и демонстрируется в следующем примере, в программе PRIME1.


Модульная программа поиска простых чисел (PRIME1).


Завершим рассмотрение PRIME0 и загрузим в окно Module (Модуль) программу PRIME1, следующий вариант программы поиска простых чисел. Начнем изучение этой программы с простого просмотра ее текста.


1. Выберите команду File|Open (Файл|Открыть).


2. По умолчанию активируется блок ввода File Name (Имя файла), содержащий шаблон имени файла вида *.EXE. Нажмите ENTER.


3. В блоке списка Files (Файлы) переместите световой маркер на PRIME1.EXE (или PRIME1PA.EXE), используя для этого клавиши «стрелка вверх» и «стрелка вниз».


4. Нажмите ENTER. Система Turbo Profiler загрузит программу PRIME1 (PRIME1PA) в окно Module (Модуль).


5. Распахните окно Module (Модуль) (нажав для этого F5). Обратите внимание на то, что в строке 4 появилась подпрограмма prime (Prime).


Сразу бросаются в глаза два отличия от предыдущего примера:


* Во-первых, исчез массив primes. Данная программа не использует способ деления на меньшие простые числа, она просто содержит в себе цикл, в котором делается попытка разделить рассматриваемое число на все нечетные числа, строго меньшие данного. Вначале результатом этого будет увеличение числа итераций по сравнению с предыдущим примером, но мы увидим, что в конечном итоге, после переработки данного примера, можно получить более рациональную и качественную программу.


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


Пометьте «области» в загруженной программе (выбрав для этого команду Add Areas|Every Line (Добавить «области»|Каждая строка) в локальном меню окна Module (Модуль)), нажмите ENTER, затем запустите PRIME1 (нажав F9) под управлением системы Turbo Profiler и ознакомьтесь с полученной статистикой. Затем выберите Display из локального меню окна Execution Profile (Профиль выполнения) для того, чтобы открыть блок диалога Display Options (Параметры изображения) и привести кнопку Both в состояние On (Используется).

Нажмите ENTER, затем распахните окно профиля (клавиша F5).


Рис. 1.6 Временная и количественная статистика. PRIME1.


Вы можете заметить, что время выполнения немного уменьшилось (отчасти это произошло за счет того, что PRIME1 выдает на экран меньше информации чем PRIME0). Самым узким местом программы попрежнему остается оператор printf (теперь находящийся в строке 21) (в PRIME1PA ему соответствует оператор Writeln в строке 24.)


Отметим, что строка, в которой непосредственно проверяется является ли очередное число простым (строка 9 в PRIME0, строка 12 в PRIME0PA), теперь выполняется 78 022 раза вместо 15 122. На первый взгляд, этот факт выглядит впечатляюще, но нужно учесть, что в результате, общее время, затрачиваемое на выполнение данной строки, увеличится всего лишь примерно на 1 секунду, так как, в соответствии с ранее полученными данными, эта строка работает очень эффективно с точки зрения использования времени.


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

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


Модификация программы и повторное профилирование.


Бентли отмечает, что вместо проверки всех множителей из интервала от 1 до n в операторе деления по модулю, достаточно ограничиться лишь числами, не превосходящими корня квадратного из рассматриваемого числа. Это и реализовано нами в программе PRIME2 (PRIME2PA).


Загрузка программы PRIME2.


Итак, продолжим наши упражнения. Для начала загрузим в окно Module (Модуль) программу PRIME2, следующую версию нашей программы. В программе PRIME2 мы добавили использование подпрограммы root (Root), библиотечной подпрограммы вычисления квадратного корня, возвращающей в качестве результата целое число.


Информация для пользователей Паскаля: Загрузите в окно программы PRIME1PA.


Вам необходимо пометить каждую строку программы как «область». Вызовите локальное меню и выберите в нем команду Add Areas| Every Line in Module (Добавить «области»| Каждая строка в модуле), затем нажмите ENTER.


Нажмите F9 для начала профилирования. На экране пользователя опять появятся все простые числа, находящиеся в диапазоне от 1 до 1000.


Когда программа закончит свою работу, откройте блок диалога Display Options (Параметры изображения) (для этого выберите команду Display (Изображение) из локального меню окна Execution Profile (Профиль выполнения)) и установите параметр Display в состояние Both («И то, и другое»). Нажмите ОК. Несмотря на уменьшение количества обращений к строке 15 (c 78022 до 5288) и соответственно сокращения общего времени, затрачиваемого на выполнение данной строки, полное время выполнения данной программы значительно возросло.


Объяснение данной особенности программы PRIME2 кроется в использовании новой подпрограммы root. Строка 7 нашей программы выполняется 5465 раз, и это занимает почти 5 секунд. На один вызов подпрограммы затрачивается приблизительно 1 милисекунда, следовательно частое обращение к этой подпрограмме является расточительным (в PRIME2PA соответствующая строка имеет номер 9.)


Если в окне Execution Profile (Профиль выполнения) временная и количественная статистика показываются одновременно, то некоторые сочетания этих данных заслуживают внимания. Для неэффективных подпрограмм вторая строка гистограммы (временные данные) гораздо длиннее, чем первая (количественные данные), это означает, что отношение времени выполнения к количеству вызовов велико. Именно такая ситуация наблюдается в строке 27, оператор printf (в Паскалевской программе это строка 28).


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


Рассматривая оператор return в подпрограмме root (строка 7), мы попадаем в другую ситуацию. Этот оператор характеризуется самым большим числом обращений и самым большим общим временем выполнения. К двум другим строкам (строке 5 и строке 8) обращение происходит 5456 раз, но гистограммы для каждой из этих строк показывают маленькие затраты времени на их выполнение. Такое сочетание нас вполне устраивает, оно означает, что данные операторы работают быстро. Итак, самой большой проблемой на настоящий момент является количество вызовов подпрограммы root.


Сокращение количества вызовов подпрограммы (PRIME3).


Теперь наша основная задача — это сокращение количества вызовов подпрограммы root. Загрузите PRIME3 в окно Module (Модуль), затем распахните это окно и посмотрите на исходный текст нашего примера.


Информация для пользователей Паскаля: Вам следует загрузить в окно Module (Модуль) пример PRIME3PA.


Единственная модификация, имеющаяся в PRIME3, содержится в подпрограмме prime. Мы добавили новую целочисленную переменную limit и, перед началом работы цикла for, присвоили ей значение, равное root(n). Переменная limit — это верхняя граница для параметра цикла for.


Информация для пользователей Паскаля: В PRIME3PA мы добавили целочисленную переменную Limit и положили ее равной корню квадратному из n перед началом выполнения цикла for. Переменная Limit — это верхняя граница для параметра цикла for.


При помощи локального меню окна Module (Модуль), пометьте «области» на каждой строке программы. Во время данного сеанса профилирования (для того, чтобы запустить программу выберите Run| Run (Выполнение| Выполнение) или нажмите клавишу F9), программа работает немного быстрее. Общее время выполнения программы PRIME3 сократилось почти на 50 %.


Теперь в качестве основного потребителя времени выполнения выступает функция printf. За счет сокращения числа обращений к подпрограмме вычисления квадратного корня (с 5456 до 999) мы значительно уменьшили общее время выполнения программы.


Добавим еще немного эффективности.


У нас осталось еще несколько возможностей для увеличения эффективности подпрограммы prime. Загрузите PRIME4 в окно Module (Модуль), затем просмотрите строки исходного текста с 8 по 17.


Информация для пользователей Паскаля: загрузите PRIME4PA и изучите строки с 11 по 32.


/* Copyright (c) 1990, Borland International */

#include <stdio.h>


prime(int n)

{

int i;


if (n % 2 == 0)

return (n==2);

if (n % 3 == 0)

return (n==3);

if (n % 5 == 0)

return (n==5);

for (i=7; i*i <= n; i+=2)

if (n % i == 0)

return 0;

return 1;

}


main()

{

int i, n;


n = 1000;

for (i=2; i<=n; i++)

if (prime(i))

printf(«%d\n», i);

}


{ Copyright (c) 1990, Borland International }

program Prime4PA;


Var

I,N: Integer;


Function Prime(N: Integer):Boolean;

Var

I: integer;

Begin

If (N MOD 2 = 0) then

Begin

Prime:= N = 2;

Exit;

End;

If (N MOD 3 = 0) then

Begin

Prime:= N = 3;

Exit;

End;

If (N MOD 5 = 0) then

Begin

Prime:= N = 5;

Exit;

End;

For I:= 7 to N-1 do

If (N MOD I = 0) then

Begin

Prime:= False;

Exit;

End;

Prime:= True;

End;


Begin

N:= 1000;

For I:= 2 to N do

If Prime(I) then

Writeln(I);

End.


В этих строках содержится ряд улучшений.


* Три оператора if в подпрограмме prime удаляют из рассмотрения числа, кратные 2, 3, и 5 соответственно. Если на основании этих проверок Вам не удается отсеять рассматриваемое число n, то приходится проверять его делимость на оставшиеся целые числа вплоть до корня квадратного из n. Начать эти проверки можно с числа 7, так как наличие операторов if исключает из рассмотрения все меньшие числа.


* Цикл for теперь работает с шагом 2, так как нет смысла рассматривать четные числа.


* Проверка i*i<=n заменила более дорогой, с точки зрения временных затрат, тест, основывающийся на вычислении квадратного корня.


Результатом всех этих изменений явилось то, что общее время выполнения уменьшилось более чем на одну секунду. Данные, приведенные на рисунке 1.7 показывают, что функция printf отнимает теперь 96 % всего времени выполнения.


Рис. 1.7 Временная и количественная статистика, PRIME4.


Сокращение времени ввода/вывода.


Изменение в доле времени, потребляемой оператором printf, говорит о том, что теперь быстродействие программы в большей степени ограничивается операторами ввода/вывода, чем ее вычислительной частью. Возможно, такое положение является вполне приемлемым. Но, хотя бы просто ради интереса, давайте попробуем выжать еще что-нибудь из этих операторов.


Загрузите PRIME5 в окно Module (Модуль) и посмотрите на 28 строку (строку 3 в Паскалевском варианте).


В Turbo C имеется более быстрый вариант функции printf, называемый cprintf, использование которого является единственным отличием программы PRIME5 от PRIME4. Функция cprintf обрабатывает ситуацию перехода на новую строку иначе, чем printf: в cprintf вместо единственного символа «новая строка» используется пара символов «возврат каретки»/«перевод строки» (\r \n).


Информация для пользователей Паскаля: система Turbo Paskal также содержит быстрый вариант Writeln (содержащийся в модуле Crt). Мы даем указание об использовании этой быстрой версии посредством включения оператора uses Crt в начало нашей программы. В этом заключено единственное отличие PRIME5PA от предыдущего варианта.


Вызовите локальное меню окна Module (Модуль) и установите «области» для каждой строки исходного текста PRIME5. Запустите PRIME5, после чего изучите количественные данные профилирования для строки 28 в PRIME5 (или строки 3 в PRIME5PA).


Более быстрый вариант функции вывода на каждых 168 вызовах экономит почти одну секунду.


Удаление пар символов cr/lf («возврат каретки»/«перевод строки») (PRIME6).


И, наконец, одно последнее изменение. Вместо того, чтобы выводить пару символов «возврат каретки»/«перевод строки» после каждого обнаруженного простого числа, попробуйте выводить просто символ «пробел». В этом и заключается единственное изменение, сделанное в программе PRIME6.


Загрузите PRIME6 (пользователям Паскаля необходимо загрузить PRIME6PA) и установите маркеры «области» на каждой строке, затем выполните эту программу.


О чудо! Удаление пар символов «возврат каретки»/«перевод строки» сократило время выполнения почти в 7 раз. Теперь стал очевиден тот факт, что переход на новую строку это дорогостоящая операция. В результате всех внесенных изменений времена выполнения и количества вызовов распределились довольно-таки равномерно.

(Рис. 1.8). Вряд ли нам удастся выжать еще что-нибудь из этой программы без основательной переработки алгоритма.


Рис. 1.8 Временная и количественная статистика программы PRIME6.


А что же дальше?


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


На этом закончим наше обучение и покинем среду системы Turbo Profiler (выберите File| Quit (Файлы| Выход) или просто нажмите Alt-X).


Для того, чтобы получить более обширную информацию о среде системы Turbo Profiler, в том числе и о тех частях профилировщика, которые не были рассмотрены в данной главе, Вам необходимо обратиться к главе 4, являющейся полным справочным руководством по среде системы Turbo Profiler.


Если у Вас возникло желание дополнительно рассмотреть еще какие-то задачи, то мы бы порекомендовали Вам сделать следующее:


* Получите профили для программ, ищущих простые числа меньшие чем:


* 2 500,


* 5 000,


* 7 500,


* 10 000.


* Установите режим профилирования в состояние пассивного анализа (выбрав для этого команду Statistics| Profiling Options (Статистика| Параметры профилирования), что повлечет за собой появление блока диалога Profiling Options (Параметры профилирования)). Какие изменения на экране профилировщика последовали за этим? Какую информацию мы теряем при пассивном анализе? (Информацию о пассивном профилировании можно почерпнуть в главе 3.)


* Посмотрите как улучшатся характеристики программы при применении решета Эратосфена для нахождения простых чисел, не превышающих 10 000.


* Сравните случай применения функции позиционирования курсора с обычным переходом на новую строку.


Существует некоторое количество статей и книг, в которых рассматривается вопрос о получении профиля программы. Книга Джона Бентли «Создание эффективных программ» (Jon Bentley, Writing Efficient Programs), содержащая сводку правил создания эффективно работающего программного кода, предлагает всестороннюю обстоятельную методологию профилирования и включает в себя обширную библиографию.







 


Главная | В избранное | Наш E-MAIL | Добавить материал | Нашёл ошибку | Другие сайты | Наверх