Требования к производительности ПО разные: для каких-то приложений она критична, для каких-то — нет. И даже если нет жестких ограничений, нам всегда хочется, чтобы наше ПО работало быстрее. Одним из методов, позволяющих выявить «узкие» места приложения для дальнейшей оптимизации, является профилирование.
На моей текущей работе в качестве основной IDE используется Qt Creator. Когда возникла необходимость использовать профилировщик в одном из проектов, я решил опробовать gprof, входящий в состав MinGW и поэтому не требующий никаких дополнительных действий по установке.
Профилирование с помощью gprof состоит из нескольких шагов:
- компилирование и сборка программы с включенной опцией профилирования,
- исполнение программы для создания файла данных о профиле,
- запуск gprof для анализа полученного файла данных о профиле.
Результатом анализа являются две таблицы — «простой профиль» (flat profile) и «граф вызовов» (call graph).
Простой профиль показывает, сколько времени программа потратила на выполнение каждой функции, и сколько раз эти функции вызывались. Если вы просто хотите узнать, какие функции самые медленные, то этот факт устанавливается именно из этой таблицы.
В графе вызовов отображается информация о каждой функции: какие функции её вызывали, какие функции и сколько раз вызывала она, а также сколько времени было затрачено на выполнение подпрограмм. Это может указать на места, в которых вы можете попытаться исключить вызовы функций, требующих много процессорного времени.
А теперь обо всем поподробнее
Чтобы скомпилировать программу для профилирования, в файл проекта Qt необходимо добавить строки:
QMAKE_CXXFLAGS_DEBUG += -pg
QMAKE_LFLAGS_DEBUG += -pg
Если использование профилировщика больше не планируется, не забываем убирать эти опции: в противном случае исполнение будет проходить несколько медленнее обычного, т.к. требуется время на сбор и запись данных о профиле.
После добавления необходимых опций в файл проекта необходимо собрать и запустить отладочную версию программы, выполнить тот функционал программы, информацию о котором вы хотите получить, и завершить работу. После завершения программы в текущей директории будет создан файл gmon.out. Отмечу, что в случае аварийного завершения работы выходной файл создан не будет.
Данные о профиле будут описывать только те части программы, которые активизировались. Например, если первой командой, которую вы ввели в вашу программу, будет команда выхода, то данные о профиле покажут время, затраченное на инициализацию и закрытие программы, но ничего более.
Полученный файл gmon.out как раз и анализируется профилировщиком gprof.
Для удобства использования настроим вызов gprof прямо из IDE:
1. Переходим на нужную панель:
Инструменты->Параметры->Среда->Внешние утилиты
2. Выбираем раздел, куда следует добавить профилировщик.
3. Настраиваем gprof:
Описание: любое понятное вам описание
Программа: полный путь к утилите gprof
Параметры: %{CurrentProject:Path}/debug/*.exe > file_name.txt
Рабочий каталог: %{CurrentProject:Path}
Должно получится что-то вроде этого:
В поле параметры при необходимости добавьте те опции, которые вам удобны. Я, например, добавляю '--brief' для вывода только самого необходимого, иначе помимо двух таблиц на выдачу пойдет еще много-много разных букв.
Пункт меню вызова профилировщика ищите в разделе меню «Инструменты», подпункте «Внешние».
После запуска gprof будет создан текстовый файл file_name.txt, куда мы перенаправили вывод анализа, и который мы сами можем внимательно изучить.
gprof в действии
Приведем пример приложения, выполняющегося достаточно продолжительное время:
void functionA()
{
int sum = 0;
while (sum < 10e5)
sum++;
}
void functionB()
{
int sum = 0;
while (sum < 30e5)
sum++;
}
int main()
{
int iterations = 1500;
while (iterations--) {
functionB();
functionA();
}
return 0;
}
Приложение содержит функции functionA и functionB, в которых выполняются длительные циклы. Функция main просто поочередно вызывает обе этих функции заданное количество раз, а именно 1500. В функции functionB цикл выполняется в три раза дольше, поэтому ожидаемый процент затраченного на нее процессорного времени составляет 75%. Давайте проверим наши ожидания, получив и проанализировав профилировщиком файл gmon.out:
Flat profile:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ms/call ms/call name
75.08 7.35 7.35 1500 4.90 4.90 functionB()
24.92 9.79 2.44 1500 1.63 1.63 functionA()
Из полученной информации видим, что на выполнение functionB тратится примерно в три раза больше времени, чем на выполнение functionA, а количество вызовов каждой функции ровно столько, сколько мы и задавали, т.е. 1500.
Более подробную информацию можно увидеть в графе вызовов:
Call graph (explanation follows)
granularity: each sample hit covers 4 byte(s) for 0.10% of 9.79 seconds
index % time self children called name
<spontaneous>
[1] 100.0 0.00 9.79 main [1]
7.35 0.00 1500/1500 functionB() [2]
2.44 0.00 1500/1500 functionA() [3]
-----------------------------------------------
7.35 0.00 1500/1500 main [1]
[2] 75.1 7.35 0.00 1500 functionB() [2]
-----------------------------------------------
2.44 0.00 1500/1500 main [1]
[3] 24.9 2.44 0.00 1500 functionA() [3]
-----------------------------------------------
Итак, основными плюсами gprof для профилирования приложений в Qt Creator являются отсутствие каких-либо дополнительных действий по установке, ведь мы уже имеем установленный MinGW, а также простота настройки и использования.
При написании статьи использовались:
— Qt Creator версии 2.4.1,
— профилировщик gprof версии 2.19.1
— материалы сайта www.cs.utah.edu/dept/old/texinfo/as/gprof.html
Автор: eazzy