Глава 2. Внутри профилировщика

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


Рассмотрим исходный текст PTOLL и PTOLLPAS:


* Copyright (c) 1990, Borland International */

#include <stdio.h>

#include <dos.h> /* contains prototype for delay() */


main()

{

printf(«Entering main\n»);

route66();

printf(«Back in main\n»);

delay(1000);

highway80();

printf(«Back in main\n»);

delay(1000);

printf(«Leaving main\n\n»);

}


route66()

{

printf(«Entering Route 66\n»);

delay(2000);

printf(«Leaving Route 66\n»);

}


highway80()

{

printf(«Entering Highway 80\n»);

delay(2000);

printf(«Leaving Highway 80\n»);

}


{ Copyright (c) 1990, Borland International }

Uses Crt;


Procedure Route66;

Begin

Writeln('Entering Route 66');

Delay(2000);

Writeln('Leaving Route 66');

End;


Procedure Highway80;

Begin

Writeln('Entering Highway 80');

Delay(2000);

Writeln('Leaving Highway 80');

End;


Begin

Writeln('Entering main');

route66;

Writeln('back in main');

Delay(1000);

highway80;

Writeln('back in main');

delay(100);

Writeln('Leaving main');

End.


Задание в качестве «областей» всех подпрограмм в рассматриваемом модуле ведет к образованию четырех переменных для подсчета времени и четырех переменных для подсчета количества вызовов.


Рис. 2.1 Переменные для подсчета времени выполнения и числа вызовов подпрограмм для PTOLL/PTOLLPAS.


Воображаемые пункты сбора подорожной пошлины.


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


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


Рассмотрим, каким образом происходит сбор временной и количественной информации для типичной программы, написанной на Паскале или С.


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


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


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


* Текущей «областью» начинает считаться route66;


* Отправляются в стек переменные, в которых накапливаются данные для вызывающей функции (в данном случае — функции main);


* Переменная, в которой подсчитывается количество вызовов route66, увеличивается на единицу.


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


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


Переменная, накапливающая количество обращений к вызывающей функции в этот момент не изменяется. Тем не менее, каждый импульс таймера, происходящий с этого момента до вызова highway80 добавляется, как к переменной, в которой накапливается время работы функции main, так и к переменной, в которой накапливается время выполнения всей программы. Для того, чтобы проверить этот факт, попробуйте убрать маркер «области», отмечающий route66 и затем сравнить данные, которые получаются в после этого, c профилем для случая, когда маркер установлен. Вы должны заметить, что общее время выполнения осталось тем же. Время же выполнения main должно увеличиться как раз на столько, сколько раньше требовалось для выполнения route 66.


Определение затрат времени на вызов подпрограмм.


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

(Также вы можете получить информацию такого рода при работе профилировщика в пассивном режиме анализа, обсуждаемом в Главе 3). А теперь, поместите курсор на первую строку route66, затем выберите команду Operation (Работа) в локальном меню окна Module (Модуль), для того, чтобы открыть блок диалога Area Options (Параметры «области»), установите Operation (Работа) в состояние Disable (Отключить), нажмите клавишу Enter.


Когда вы отключаете сбор статистики при входе в route66, при возврате из этой подпрограммы, сбор данных автоматически не возобновляется. Вы должны установить маркер «области» на закрывающую фигурную скобку функции route66, и снова установить параметр Operation (Работа) для данного маркера «области» в состояние Enable.


Кто расплачивается за циклы.


Метафора пунктов сбора подорожной пошлины помогает объяснить почему не изменяется текущая «область» после перехода маркера «области» с последующим возвратом назад (происходящим в результате выполнения оператора goto или цикла) по адресу, предшествующему этому маркеру, хотя, c лексической точки зрения, вы находитесь вне зоны действия маркера, вы еще не встретили на своем пути какой-либо маркер. И поэтому каждый генерируемый импульс таймера продолжает ассоциироваться с последним пройденным маркером.


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

#include <stdio.h>

#include <dos.h> /* contains prototype for delay() */


main()

{

printf(«Entering main\n»);

lostintown();

delay(1000);

printf(«Leaving main\n\n»);

delay(1000);

}


lostintown()

{

int i;

printf(«Looking for highway…\n»);

delay(100);

for (i=0; i<10; i++)

{

printf(«Ask for directions\n»);

printf(«Wrong turn\n\n»);

delay(1000);

}

printf(«On the road again\n»);

}


{ Copyright (c) 1990, Borland International }

Uses Crt;


Procedure Lostintown;

Var

I: integer;

Begin writeln('Looking for highway…');

delay(100);

for I:= 0 to 9 do

Begin

Writeln('Ask for directions');

Writeln('Wrong turn');

Writeln;

delay(1000);

End;

Writeln('on the road again');

End;


Begin

Writeln('Entering Main');

Lostintown;

Delay(1000);

writeln('Leaving main');

writeln;

delay(1000);

End.


В программе plost мы усложнили подпрограмму lostintown использованием составного оператора внутри цикла. Предположим, что были установлены 3 маркера: один для функции main, один для lostintown и еще один для оператора, печатающего Wrong turn.


После того, как вы попадаете в lostintown, начинают происходить странные вещи. Когда вы входите в эту подпрограмму lostintown становиться текущей «областью». Время, приходящиеся на печать фразы «looking for highway» ассоциируется с этим маркером.


При входе в цикл время продолжает ассоциироваться с маркером подпрограммы и в первый раз, когда вы выполняете оператор печати строки «Ask for direction», время все еще относится к этому маркеру. Тем не менее стоит вам пройти маркер оператора печати «Wrong turn», все оставшееся время работы подпрограммы будет ассоциироваться с этим маркером.


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


Например, если вы установили три маркера для программы plost, так как мы описали (по одному маркеру для функций main, lostintown и оператора печати строки «Wrong Turn»), то приблизительно 84 % общего времени выполнения программы будет ассоциировано с печатью строки «Wrong turn», и только 1 % времени выполнения будет связан с lostintown. Это происходит потому, что девять из десяти выполнений оператора печати строки «Ask for direction», а также все последующие операторы delay ассоциируются с маркером, помечающим оператор печати строки «Wrong Turn».


Если вы уберете маркер «области», которым помечен оператор печати строки «Wrong Turn», то 84 % остающегося времени будет затрачено на выполнение подпрограммы lostintown.


Рассмотрим следующую программу:


main.

{

while(!kbhit())

{

func1();

statement1;

statement2;

func2();

}

}


func1()

{

}


func2()

{

}


Предположим, что маркеры области в рассматриваемой программе, установлены для всех подпрограмм. Каждое из имен функции main, func1 и func2 в описаниях этих функций соответствует началу «области» и таймер генерирует импульсы с частотой 100 раз в секунду. (Предположим, что 1/100 секунды это достаточно большой промежуток времени для того, чтобы вы могли наблюдать за происходящим процессом.)


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


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


Когда вы возвращаетесь из функции func1, вы проходите маркер возврата, установленный профилировщиком в момент входа в func1.

Функция main снова становится текущей «областью». Каждый такт таймера, во время которого выполняется statement1 или statement2, считается относящимся к «области», задаваемой функцией main.


На этом этапе происходят две вещи:


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


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


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


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


Что же вам делать в случае нескольких операторов return? Ответ на этот вопрос заключается в использовании единственной настоящей точки возврата только в самом конце тела функции.


Важное замечание о точках возврата:


Хотя вы формально и можете задать в функции несколько точек возврата, компилятор Turbo C, на самом деле, преобразует все эти точки в переходы на единственную настоящую точку возврата, расположенную в самом конце функции. При установке маркера «области» на каком-либо операторе возврата на самом деле помеченной оказывается строка ассемблерного кода, соответствующая закрывающей фигурной скобке, которой завершается описание данной функции. Эта строка представляет собой ассемблерный вариант оператора возврата, на который осуществляется переход из всех остальных точек функции, где встречается оператор return.


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


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


Сбор статистических данных о вызовах подпрограмм.


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


Для того, чтобы поддерживать стек вызовов подпрограмм в системе Turbo Profiler имеется два типа маркеров «области»:


* маркер «области» типа «точка входа в подпрограмму» (маркер подпрограммы);


* обычный маркер «области» (маркер типа метка)


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


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

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


Когда, в процессе выполнения, в теле активной подпрограммы встречается оператор возврата, маркер «области», относящийся к данной подпрограмме, переходит в пассивное состояние.


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


Таким образом, профилировщик может прослеживать полную предысторию вызовов для каждой помеченной подпрограммы. Если Вы активировали параметр Statistics| Callers (Статистика| Вызывающие подпрограммы) для всех помеченных подпрограмм, то каждый раз при встрече маркера «области», типа «точка входа в подпрограмму», профилировщик полностью сохраняет свой стек вызовов в буфере, связанном с данной подпрограммой.


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


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


* параметр Statistics| Callers (Статистика| Вызывающие подпрограммы) (может находиться в состоянии Enabled (Включено) или Disabled (Выключено);


* параметр Callers (Вызывающие подпрограммы) для каждой помеченной «области» в окне Areas («Области»).


Более гибкие возможности для контроля путей вызова подпрограмм Вы получаете при использовании локальных меню в окнах Module (Модуль) и Areas («Области»). Вы можете установить для каждой из помеченных «областей» свое собственное значение параметра Callers (Вызывающие подпрограммы). Как команда Callers (Вызывающие подпрограммы) локального меню окна Module (Модуль), так и команда Options (Параметры) локального меню окна Areas («Области») ведут к появлению на экране блока диалога, в котором Вы можете задать одно из следующих значений параметра Callers (Вызывающие подпрограммы): All Callers (Все вызывающие подпрограммы), Immediate Caller (Непосредственно вызывающая подпрограмма) или None (Никакие).


* All Callers (Все вызывающие подпрограммы) означает сохранение полного стека вызовов подпрограмм каждый раз, когда в процессе выполнения, встречается точка входа в помеченную подпрограмму.


* Immediate Caller (Непосредственно вызывающая подпрограмма) означает запоминание только самого верхнего элемента стека вызовов подпрограмм при попадании на точку входа в данную помеченную подпрограмму.


* None (Никакие) означает, что при встрече, в процессе выполнения, маркера «области» типа «точка входа в подпрограмму» никакая информация из стека вызовов подпрограмм не запоминается.


По умолчанию, при первом получении профиля программы параметр Callers (Вызывающие подпрограммы) для всех маркеров «области» типа «точка входа в подпрограмму» имеет значение None (Никакие).


Установка параметра Statistics| Callers (Статистика| Вызывающие подпрограммы), находящегося в главном меню, в состояние Enable (Включено) равнозначна установке значения All Callers (Все вызывающие подпрограммы) параметра Сallers (Вызывающие подпрограмм) для каждого маркера «области», установленного в окне Areas («Области»). Тем не менее, если Вы вручную установили значения параметров Callers (Вызывающие подпрограмм) для каких-либо «областей» в окне Areas («Области»), то, в этом случае, установка параметра Statistics| Callers (Статистика| Вызывающие подпрограммы) в состояние Enable (Включено), не изменит значение параметра Callers (Вызывающие подпрограммы) для таких «областей».


Установка параметра Statistics| Callers (Статистика| Вызывающие подпрограммы) в некоторой точке программы в состояние Disable (Отключено) указывает профилировщику, чтобы он не сохранял никакой информации из стека вызовов, но это совершенно не влияет на действие параметров Caller (Вызывающая подпрограмма), установленных в окне Areas («Области») (как и при установке данного параметра в состояние Enable (Включено)).


Опрос вместо подсчета.


На самом деле, профилировщик не измеряет время непосредственно, а очень точно вычисляет его на основании информации о количестве прерываний таймера.


Этот способ подсчета времени можно рассматривать как один из видов статистического моделирования (некий статистический опрос).

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


Примечание: Все вышесказанное имеет место только в случае работы в пассивном режиме профилирования.


Профилировщику известно общее время выполнения программы.

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


<время выполнения «области»> = <общее время выполнения программы> * <значение счетчика «области»> / <значение счетчика программы>


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


Если Вы подозреваете, что возникла подобная ситуация с наложением частот, то измените значение параметра Statistics|

Profiling Options| Clock speed value (Статистика| Параметры профилирования| Скорость работы часов) и сравните профиль, полученный после внесения этого изменения, с предыдущим профилем.


Использование памяти профилировщиком.


На следующем рисунке показано распределение памяти при получении профиля программы с помощью системы Turbo Profiler.


Рис. 2.2 Карта памяти системы Turbo Profiler.


Для запоминания информации об «областях» профилировщик использует динамически-распределяемую область памяти (far heap).

Если Вы, в процессе выполнения программы, добавите новые «области», то эта, динамически-распределяемая область памяти, может распространиться на ту часть памяти, которая зарезервирована для программы пользователя, для того, чтобы разместить в ней новые переменные «областей» и буферы. Поэтому, если Вы вносите изменения в разметку «областей» во время выполнения программы, Вам необходимо повторно запустить программу с помощью Run| Program Reset (Выполнение| Повторное выполнение программы). Если Вы этого не сделаете, то результаты профилирования будут совершенно непредсказуемы, Ваш компьютер может просто «зависнуть».







 


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