Профилятор gprof: Как разобраться в графе вызовов

Вперед Назад Содержание

7. Как разобраться в графе вызовов

 * Первичная строка:: Описание содержимого первичной строки.
 * Вызывающие::       Описание содержимого строк вызывающих функций.
 * Подпрограммы::     Описание содержимого строк вызываемых функций.
 * Циклы::            Описание взаимно-рекурсивных функций, таких, как
                      `a' вызывает `b', а `b' вызывает `a'.

7.1 Первичная строка

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

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

      index  % time    self  children called     name
      ...
      [3]    100.0    0.00    0.05       1         report [3]
Что же означают поля в первичной строке:
`index' (`индекс')

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

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

`% time' (`% времени')

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

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

`self' (`собственных секунд')

Это количество секунд выполнения, подсчитанных только для этой функции. Оно должно быть идентично числу, указанному в поле `собственных секунд' в простом профиле для этой функции.

`children' (`секунд в подпрограммах')

Это количество секунд выполнения, подсчитанных только для вызванных этой функцией подпрограмм. Оно должно быть идентично сумме `собственных секунд' и `секунд в подпрограммах' для всех укзанных ниже вызываемых функций [в этой записи].

`called' (`вызывалась')

Это общее количество вызовов этой функции--сколько раз она была вызвана.

Если эта функция вызывает себя рекурсивно, то будут указаны два числа, разделенные `+'. Первое число показывает количество нерекурсивных вызовов, а второе--рекурсивных.

В вышеприведенном примере функция `report' была один раз вызвана из функции `main'.

`name' (`имя функции')

Это имя текущей функции. Индексный номер повторяется после него.

Если функция является частью цикла взаимной рекурсии, то номер цикла выводится между именем функции и индексным номером (*См. Циклы::). Например, если функция `gnurr'--часть цикла номер один и имеет индекс `двенадцать', то первичная строка ее записи будет завершена так:

           gnurr <cycle 1> [12]

7.2 Строки для вызывающей функции

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

Для изучения мы повторим две строки из записи для функции `report'--первичную строку и одну из предшествующих строк вызывающей функции, вместе со строками заголовков, содержащих имена полей:

      index  % time    self  children called     name
      ...
                      0.00    0.05       1/1           main [2]
      [3]    100.0    0.00    0.05       1         report [3]
Что же означают поля в строке вызывающей функции (для `report', вызванной из `main'):
`self' (`собственных секунд')

Оценка времени, потраченного самой функцией `report', когда она была вызвана из функции `main'.

`children' (`секунд в подпрограммах')

Оценка времени, потраченного подпрограммами функции `report', когда `report' была вызвана из функции `main'.

Сумма `собственных секунд' и `секунд в подпрограммах' является приблизительной оценкой времени выполнения `report', вызываемой из `main'.

`called' (`вызывалась')

Два числа: количество вызовов `report' из `main', за которым следует общее количество нерекурсивных вызовов `report' изо всех функций, которые ее вообще вызывали.

`name and index number' (`имя функции и ее индекс')

Имя функции, вызвавшей `report', к которой относится эта строка, и следом указан индекс этой функции.

Не все функции имеют записи в графе вызовов; некоторые из опций `gprof' требуют пропустить некоторые функции. Если вызывающая функция не имеет записи о себе, она, тем не менее, имеет строку вызывающей функции в записях тех функций, которые она вызывает.

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

В случае, когда невозможно определить вызывающую функцию, выводится пустая строка о вызывающей функции, в которой `имя вызывающей' дается как `<spontaneous>' (`<непосредственно>' либо `<а вот!>', смотря как переводить), а все остальные поля оставлены пустыми. Это может произойти, например, для обработчиков сигналов (signal handlers).

7.3 Строки для подпрограмм функции

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

Для изучения мы повторим две строки из записи для функции `main'-- первичную строку и одну из последующих строк вызванной ею функции, вместе со строками заголовков, содержащих имена полей:

      index  % time    self  children called     name
      ...
      [2]    100.0    0.00    0.05       1         main [2]
                      0.00    0.05       1/1           report [3]
Что же означают поля в строке вызванной функции (для `main', вызывающей `report'):
`self' (`собственных секунд')

Оценка времени, потраченного самой функцией `report', когда она была вызвана из функции `main'.

`children' (`секунд в подпрограммах')

Оценка времени, потраченного подпрограммами функции `report', когда `report' была вызвана из функции `main'.

Сумма `собственных секунд' и `секунд в подпрограммах' является приблизительной оценкой времени выполнения `report', вызываемой из `main'.

`called' (`вызывалась')

Два числа: количество вызовов `report' из `main', за которым следует общее количество нерекурсивных вызовов `report'.

`name' (`имя функции')

Имя функции, вызванной из `main', к которой относится эта строка, и следом указан индекс этой функции.

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

7.4 Как описываются взаимно-рекурсивные функции

Картина может быть осложнена наличием "циклической рекурсии" в графе вызовов. Цикл возникает, когда некоторая функция вызывает другую функцию, которая (прямо или опосредованно) вызывает (или пытается это сделать) первую. Например, если `a' вызывает `b', а затем `b' вызывает `a', то `a' и `b' организуют циклическую рекурсию.

Когда бы вызовы ни прошли по обоим путям между двумя функциями, они принадлежат к одному и тому же циклу. Если `a' и `b' вызывают друг друга, и `b' и `c' вызывают друг друга, то все три функции находятся в одном и том же цикле. Однако следует заметить, что даже если только `b' вызывает `a', но при этом сама не была вызвана из `a', то `gprof' не способен распознать это, и, таким образом, `a' и `b' все же считаются в цикле.

Циклы нумеруются последовательными целыми числами. Если функция принадлежит циклу, всякий раз, когда ее имя встречается в графе вызовов, за ним следует `<cycle NUMBER>' (`<цикл НОМЕР>')

Циклы создают проблему, заключающуюся в том, что циклы делают парадоксальными значения времен в графе вызовов. Поле `время в подпрограммах' для `a' должно включать в себя время, затраченное на вызываемую `b' и все ее подпрограммы--но одной из подпрограмм `b' является `a'! А сколько времени выполнения `a' должно быть включено во `время в подпрограммах' функции `a', если `a' непосредственно рекурсивна?

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

Вот пример части графа вызовов, в котором показан цикл, содержащий функции `a' и `b'. Цикл начинался вызовом `a' из `main'; обе, `a' и `b', вызывали `c'.

      index  % time    self  children called     name
      ----------------------------------------
                       1.77        0    1/1        main [2]
      [3]     91.71    1.77        0    1+5    <cycle 1 as a whole> [3]
                       1.02        0    3          b <cycle 1> [4]
                       0.75        0    2          a <cycle 1> [5]
      ----------------------------------------
                                        3          a <cycle 1> [5]
      [4]     52.85    1.02        0    0      b <cycle 1> [4]
                                        2          a <cycle 1> [5]
                          0        0    3/6        c [6]
      ----------------------------------------
                       1.77        0    1/1        main [2]
                                        2          b <cycle 1> [4]
      [5]     38.86    0.75        0    1      a <cycle 1> [5]
                                        3          b <cycle 1> [4]
                          0        0    3/6        c [6]
      ----------------------------------------
(Полный граф вызовов для этой программы содержит также запись для `main', которая вызывает `a', и запись для `c', которую вызывают `a' и `b').

      index  % time    self  children called     name
                                                   <spontaneous>
      [1]    100.00       0     1.93    0      start [1]
                       0.16     1.77    1/1        main [2]
      ----------------------------------------
                       0.16     1.77    1/1        start [1]
      [2]    100.00    0.16     1.77    1      main [2]
                       1.77        0    1/1        a <cycle 1> [5]
      ----------------------------------------
                       1.77        0    1/1        main [2]
      [3]     91.71    1.77        0    1+5    <cycle 1 as a whole> [3]
                       1.02        0    3          b <cycle 1> [4]
                       0.75        0    2          a <cycle 1> [5]
                          0        0    6/6        c [6]
      ----------------------------------------
                                        3          a <cycle 1> [5]
      [4]     52.85    1.02        0    0      b <cycle 1> [4]
                                        2          a <cycle 1> [5]
                          0        0    3/6        c [6]
      ----------------------------------------
                       1.77        0    1/1        main [2]
                                        2          b <cycle 1> [4]
      [5]     38.86    0.75        0    1      a <cycle 1> [5]
                                        3          b <cycle 1> [4]
                          0        0    3/6        c [6]
      ----------------------------------------
                          0        0    3/6        b <cycle 1> [4]
                          0        0    3/6        a <cycle 1> [5]
      [6]      0.00       0        0    6      c [6]
      ----------------------------------------
Поле `self' в первичной строке цикла--общее время выполнения всех функций цикла. Оно равно сумме полей `self' для каждой отдельной функции цикла, взятых из записей вызываемых функций.

Поле `children' в первичной строке цикла и строки подпрограмм содержат времена только тех функций, которые не входят в цикл. Хотя `a' и вызывает `b', время таких вызовов `b' не подсчитывается для `a' в поле `children'. Таким образом, мы не натыкаемся на проблему: что делать, если время в этих вызовах `b' включает опосредованные рекурсивные вызовы `a'.

Поле `children' в строках вызывающих функций в записи цикла оценивают время, затрачиваемое *на весь цикл в целом*, и на другие его подпрограммы, в том случае, когда эта функция вызывает одну из функций цикла.

Поле `calls' в первичной строке цикла содержит два числа: во-первых, количество вызовов функций цикла функциями извне цикла; во-вторых, количество вызовов функций цикла функциями цикла (включая количество вызовов самой себя). Это обобщение обычного разделения вызовов на рекурсивные и нерекурсивные.

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

В отдельных записях для каждой функции цикла могут встречаться другие функции, указанные и как вызывающие, и как вызываемые. Эти строки показывают, сколько раз каждая из функций цикла вызывала или была вызвана из других функций цикла. Поля `self' и `children' таких строк не заполняются, так как затруднительно определить их смысл (значение), когда начинается рекурсия.


Вперед Назад Содержание