Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Как установить и использовать профилировщик Gprof в Linux

Оригинал: How to install and use profiling tool Gprof on Linux
Автор: Himanshu Arora
Дата публикации: 18 ноября 2016 г.
Перевод: А.Панин
Дата перевода: 6 марта 2017 г.

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

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

Если вы являетесь разработчиком программного обеспечения, используете язык программирования C, Pascal или Fortran 77 и дистрибутив Linux в качестве платформы разработки, вам будет полезно знать о существовании мощного инструмента для проверки производительности вашего кода под названием Gprof. В рамках данной статьи мы будем подробно обсуждать процесс загрузки, установки и использования данного инструмента.

Перед тем, как перейти к рассмотрению обозначенных вопросов, следует упомянуть о том, что все примеры и инструкции из данной статьи были протестированы в дистрибутиве Ubuntu 14.04 LTS с Gprof версии 2.24.

Что такое Gprof?

Итак, что же такое Gprof? В соответствии с официальной документацией проекта, данный инструмент позволяет пользователям получить данные профилирования программ на языках C, Pascal и Fortran77. По сути, Gprof рассчитывает количество времени, которое проходит в процессе исполнения каждой из процедур или функций. "После этого полученные измерения распределяются по вершинам графа вызовов. В процессе выявляются циклы и вызовам из них ставится в соответствие время исполнения этих циклов."

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

Загрузка и установка Gprof

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

$ gprof

Если в результате вы получите сообщение об ошибке:

$ a.out: No such file or directory

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

$ apt-get install binutils

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

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

Учтите, что приведенный выше код (test_gprof.c) писался специально для демонстрации возможностей Gprof; он не был взят из какого-либо из существующих проектов.

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

$ gcc -Wall -std=c99 test_gprof.c -o test_gprof

Но так как мы планируем осуществлять профилирование кода с помощью Gprof, нам придется передавать компилятору GCC дополнительный параметр -pg. По этой причине команда компиляции приобретет следующий вид:

$ gcc -Wall -std=c99 -pg test_gprof.c -o test_gprof

Если вы обратитесь к странице руководства компилятора GCC, вы сможете обнаружить на ней следующую информацию о параметре -pg:

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

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

$ ./test_gprof

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

$ ls gmon*
gmon.out

Это файл, в котором содержится вся информация, необходимая утилите Gprof для вывода информации о профилировании в читаемом человеком формате. А теперь воспользуемся Gprof следующим образом:

$ gprof test_gprof gmon.out > profile-data.txt

По сути, данная команда имеет следующий синтаксис:

$ gprof [имя-исполняемого-файла] gmon.out > [имя-файла-с-данными-профилирования]

Но перед тем, как приступить к рассмотрению информации из файла profile-data.txt следует упомянуть о том, что читаемый человеком вывод Gprof разделен на две части: данные профилирования и граф вызовов. А это выдержка со страницы руководства Gprof, относящаяся к информации из данных частей вывода:

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

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

Вооружившись этой информацией, вы сможете лучше понять назначение данных из файла профилирования (в моем случае это файл с именем profile-data.txt). А это полученная мною часть данных профилирования:

Each sample counts as 0.01 seconds.
 %    cumulative self           self    total 
 time seconds    seconds calls  ms/call ms/call  name 
 96.43 0.81      0.81      1    810.00  810.00   func3
 3.57 0.84       0.03      1    30.00   840.00   func1
 0.00 0.84       0.00      1    0.00    810.00   func2
 0.00 0.84       0.00      1    0.00    0.00     func4

И описание полей таблицы:

Название Назначение
% time Процентное отношение времени, затраченного на исполнение данной функции, к общему времени работы программы.
cumulative seconds Общее количество секунд, затраченное на исполнение данной функции и функций выше по списку.
self seconds Количество секунд, затраченное на исполнение лишь данной функции. Это первичный параметр сортировки списка функций.
calls Количество вызовов данной функции в случае профилирования, пустое поле в противном случае.
self ms/call Среднее количество миллисекунд, проведенное в данной функции после каждого из вызовов в случае профилирования, пустое поле в противном случае.
total ms/call Среднее количество миллисекунд, проведенное в данной функции и ее подфункциях после каждого из вызовов в случае профилирования, пустое поле в противном случае.
name Имя данной функции. Это вторичный параметр сортировки списка функций. Индексы влияют на расположение функции в списке gprof. Индекс в круглых скобках указывает позицию функции в списке gprof в случае вывода.

Это полученный мною граф вызовов:

granularity: each sample hit covers 4 byte(s) for 1.19% of 0.84 seconds

index % time self children called name
             0.03 0.81     1/1       main [2]
[1]    100.0 0.03 0.81      1     func1 [1]
             0.00 0.81     1/1       func2 [3]
-----------------------------------------------
                                     <spontaneous>
[2]    100.0 0.00 0.84            main [2]
             0.03 0.81     1/1       func1 [1]
             0.00 0.00     1/1       func4 [5]
-----------------------------------------------
             0.00 0.81     1/1       func1 [1]
[3]    96.4  0.00 0.81      1     func2 [3]
             0.81 0.00     1/1       func3 [4]
-----------------------------------------------
             0.81 0.00     1/1       func2 [3]
[4]    96.4  0.81 0.00      1      func3 [4]
-----------------------------------------------
             0.00 0.00     1/1       main [2]
[5]    0.0   0.00 0.00      1      func4 [5]

Ниже приведено описание столбцов данного графа:

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

Столбцы строки с информацией о текущей функции:

Название Назначение
index Уникальное число, поставленное в соответствие данному элементу таблицы. Индексы сортируются по возрастанию. Индексы выводятся рядом с каждым из имен функции, что упрощает поиск функций в таблице.
% time Процентное отношение "всего" времени, проведенного в рамках данной функции, к времени, проведенному в дочерних функциях. Обратите внимание на то, что ввиду существования различных механизмов исследования кода и возможности исключения функций из рассмотрения, данные значения зачастую не достигают 100%.
self Общее количество времени, проведенного в рамках данной функции.
children Общее количество времени, проведенного в рамках дочерних функций.
called Количество вызовов данной функции. В случае осуществления рекурсивных вызовов, данное значение будет отражать количество не рекурсивных вызовов, после которого будет размещен символ "+" с значением, отражающим количество рекурсивных вызовов.
name Имя текущей функции. Индекс выводится после него. Если функция является частью цикла, номер цикла будет выводиться между именем функции и индексом.

Элементы с описаниями родительских функций имеют следующие поля:

Название Назначение
self Количество времени, проведенного в данной родительской функции.
children Количество времени, проведенного в дочерней функции данной функции.
called Количество вызовов, осуществленных из данной родительской функции / общее количество вызовов данной функции. Количество рекурсивных вызовов функции не отражается в значении после /.
name Имя родительской функции. Индекс родительской функции выводится после него. Если родительская функция является частью цикла, номер цикла будет выводиться между именем функции и индексом.

Если родительскую функцию текущей функции не удается обнаружить, в поле name выводится слово <spontaneous>, а все остальные поля остаются пустыми. Элементы с описаниями дочерних функций имеют следующие поля:

Название Назначение
self Количество времени, проведенного в данной дочерней функции.
children Количество времени, проведенного в дочерней функции данной функции.
called Количество вызовов данной дочерней функции, осуществленных из родительской функции / общее количество вызовов данной функции. Количество рекурсивных вызовов данной дочерней функции не отражается в значении после /.
name Имя дочерней функции. Индекс дочерней функции выводится после него. Если дочерняя функция является частью цикла, номер цикла будет выводиться между именем функции и индексом.

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

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

Заключение

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