Ускорение кода при помощи GNU-профайлераИсточник: IBM developerWorks Россия Мартин Ханифорд
Оглавление
ВведениеТребования к производительности программного обеспечения очень разные, но вероятно не секрет, что многие приложения имеют очень жесткие требования по скорости выполнения. Видеопроигрыватели являются хорошим примером: видеопроигрывателем не очень то можно пользоваться, если он проигрывает видеофильм на 75% от требуемой скорости. Другие приложения (например, кодирование видеоинформации), выполняющие длительные операции, лучше использовать в пакетном режиме, когда вы запускаете программу и оставляете ее работать в фоновом режиме, занимаясь в это время чем-нибудь еще. Хотя эти типы приложений не имеют таких жестких ограничений по производительности, увеличение производительности все равно дает преимущества, например, способность кодировать большее количество видеоинформации за определенный промежуток времени или способность кодировать с более высоким качеством за то же время. В общем случае для всех приложений, кроме простейших, работает правило: чем выше производительность, тем более полезным и популярным будет приложение. По этой причине анализ производительности является (или должен являться) важнейшей задачей для многих прикладных разработчиков. К сожалению, большинство усилий, затрачиваемых на попытки ускорить приложение, проходит даром, поскольку разработчики часто оптимизируют свои программы на микроуровне без полного исследования их функционирования на макроуровне. Например, вы можете потратить много времени на ускорение работы определенной функции в два раза, что конечно правильно, но если эта функция вызывается редко (скажем при открытии файла), то уменьшение времени ее выполнения с 200 мс до 100 мс существенно не скажется на общем времени выполнения программы. Более эффективным использованием вашего времени была бы оптимизация тех участков программы, которые вызываются наиболее часто. Например, если приложение тратит 50% времени на функции обработки строк, и вы можете оптимизировать эти функции на 10%, то это даст 5%-ное улучшение общей производительности приложения. Следовательно, очень важно иметь точную информацию о том, где именно расходуется время в вашем приложении (и для реальных входных данных ), если вы хотите действительно эффективно его оптимизировать. Такие действия называются профилированием кода . Эта статья знакомит с программой профилирования, предоставляемой вместе с набором инструментальных средств проекта GNU для компилирования, называемой GNU-профайлером (gprof). Спасительный gprofПеред началом изучения правил использования gprof важно знать, где происходит профилирование во всем цикле разработки. В общем случае код должен писаться для решения следующих трех задач, перечисленных в порядке их важности:
Предположим, что вы уже имеете работающее приложение. Рассмотрим, как использовать gprof для точного измерения и поиска участков, на которые расходуется время при выполнении приложения, для того чтобы наиболее эффективно потратить усилия на оптимизацию. gprof может профилировать приложения, написанные на языках C, C++, Pascal и Fortran 77. Приведенные здесь примеры используют C. Листинг 1. Пример долго выполняющегося приложения
Как видно из этого кода, это очень простое приложение содержит две функции, Для разрешения профилирования просто добавьте параметр
После компоновки приложения запустите его обычнм способом:
После завершения работы приложения вы должны увидеть файл gmon.out, созданный в текущем каталоге. Использование результатовПрежде всего, просмотрите "простой профиль" (flat profile), который вы получаете при выполнении команды
Результаты работы таковы:
Из полученной информации вы можете увидеть, что, как и ожидалось, примерно в четыре раза больше времени тратится на функцию Проницательные читатели заметят, что многие вызовы функций (например Затем вы, возможно, захотите увидеть "граф вызовов" (call graph), который можно получить следующим образом:
Результаты работы таковы:
Наконец, вы можете получить листинг "аннотированный исходный код" (annotated source), в котором выводится исходный код приложения с отметками о количестве вызовов каждой функции. Для использования этой возможности откомпилируйте исходный код с разрешенной отладочной информацией, для того чтобы исходный код был помещен в исполняемый файл:
Снова выполните приложение:
Команда
Результаты работы: Листинг 4. Аннотированный исходный код
Поддержка разделяемых библиотекКак я уже упоминал ранее, поддержка профилирования добавляется компилятором, поэтому, если вы хотите получить информацию для профилирования из любой разделяемой библиотеки, включая библиотеку C (libc.a), вы должны тоже откомпилировать ее с параметром В используемом мной дистрибутиве, gentoo, необходимо добавить "profile" в флаги USE и пересобрать glibc. После этого вы должны увидеть, что создалась библиотека /usr/lib/libc_p.a. Для тех дистрибутивов, которые не поставляются с библиотекой libc_p в стандартном варианте, вы должны проверить, можно ли установить ее отдельно, либо загрузить исходный код glibc и скомпоновать ее самостоятельно. После получения файла libc_p.a вы можете просто повторно скомпоновать ваш пример следующим образом:
Если вы опять запустите приложение и получите простой профиль или граф вызовов, то должны увидеть множество функций времени исполнения C, включая printf (ни одна из которых не является важной для нашей тестовой программы). Пользовательское время или время ядраТеперь, зная как использовать gprof, вы можете очень просто и эффективно выполнять профилирование ваших приложений для анализа и, будем надеяться, для устранения слабых мест в производительности. Однако сейчас вы должны обратить внимание на одно из самых важных ограничений gprof: во время выполнения приложения он профилирует только время пользовательского режима. Обычно приложения тратят некоторое количество времени на пользовательский код и некоторое количество на "системный код", например, на системные вызовы ядра. Если вы немного измените листинг 1, то сможете более четко увидеть эту проблему: Листинг 5. Добавление системных вызовов в листинг 1
Я изменил код таким образом, что вместо выполнения циклов функции Откомпилируйте это приложение:
и выполните его с 30-ю итерациями:
Вы получите следующий простой профиль: Листинг 6. Простой профиль, показывающий сиcтемные вызовы
Анализируя этот результат, вы можете увидеть, что хотя профайлер и зарегистрировал правильно количество вызовов каждой функции, время работы этих функций (и, в конечном счете, всех функций) равно 0.00. Причина этого заключается в том, что функция В этом есть как плюсы, так и минусы. С одной стороны, трудно оптимизировать приложения, тратящие большую часть времени в пространстве ядра, или работающие медленно из-за внешних факторов, таких как перегрузка подсистемы ввода/вывода операционной системы. С другой стороны, это означает, что профилирование не подвержено влиянию всех остальных происходящих в системе событий (например, если другой пользователь занимает много времени CPU). В общем случае хорошей оценкой полезности применения gprof для оптимизации вашего приложения является его запуск с командой Запустите программу, приведенную в листинге 2, следующим образом:
Вы получите следующий результат: Листинг 7. Результат команды time
Из этого видно, что на выполнение программы в пространстве пользователя время почти не тратится, поэтому gprof не будет полезна в данном случае. ЗаключениеНесмотря на ограничения, отменные выше, gprof может быть очень полезным средством оптимизации вашего кода, особенно если ваш код работает в основном в пространстве пользователя. Обычно хорошей идеей является сначала запуск вашего приложения с командой Если программа gprof не подходит для профилирования вашего приложения, существуют альтернативные программы, не имеющие ограничений gprof, включая OProfile и Sysprof. С другой стороны (если предположить, что у вас установлен пакет gcc), одним из главных преимуществ gprof над альтернативными программами является то, что, вероятнее всего, она уже установлена на любой используемой вами системе Linux. |