Шустрый, удобный и кроссплатформенный профилировщик C++ кода
Скриншот профилирования примера из SDK CryEngine
Существующие решения нам не подходили по ряду причин. Нам нужен был качественный профайлер, умеющий делать следующее:
- Профилировать выбранные участки кода
- Работать на нескольких платформах
- Учитывать переключение контекста
- Требовать минимальных дополнительных затрат памяти во время профилирования
- Не накладывать дополнительных временных ограничений во время выполнения приложения. Согласитесь, если профилировщик будет работать дольше, чем профилиуремый косочек кода, то можно сделать некорректные выводы.
В результате тщательной проработки появился на свет профайлер, умеющий делать всё вышеперечисленное, и даже больше!
Если вы хотите знать, сколько времени работает ваш код, и иметь при этом объективные доказательства — прошу под кат, где я покажу, как использовать профилировщик.
Интегрирование в код
- Качаем и распаковываем свежий релиз отсюда: https://github.com/yse/easy_profiler/releases
- Прописываем компилятору директорию для поиска заголовочных файлов:
/include - Прописываем компоновщику директорию для поиска библиотек:
/bin - Добавляем definition компилятору:
BUILD_WITH_EASY_PROFILER
- Добавляем блоки в те места кода, которые хотим замерить. Например:
#include
void foo() { EASY_FUNCTION(profiler::colors::Magenta);// Начать блок с именем, совпадающим с именем функции EASY_BLOCK("Calculating sum");// Блок с цветом по умолчанию int sum = 0; for (int i = 0; i < 10; ++i) { EASY_BLOCK("Addition", profiler::colors::Red);// Блок будет закончен при выходе из области видимости sum += i; } EASY_END_BLOCK; // Закончить блок (в данном случае блок с именем "Calculating sum" EASY_BLOCK("Calculating multiplication", profiler::colors::Blue500); int mul = 1; for (int i = 1; i < 11; ++i) mul *= i; //на выходе из функции автоматически будут закрыты все открытые и незавершённые в этой функции блоки. В данном примере, автоматически закроются блоки с именами "Calculating multiplication" и "foo" } - Не забываем положить рядом с собранным приложением библиотеку easy_profiler (*.dll или *.so). Или прописываем в системную переменную
PATH
(в линуксе достаточно вLD_LIBRARY_PATH
) директорию/bin
Добавленные блоки в режиме сбора статистики занимают минимально-возможное время (как мы этого добились — в дальнейших статьях о технической реализации). На машине с процессором Core i7–5930K 3.5GHz, 16 Gb RAM, Win7 Pro в приложении с 12 потоками средняя «стоимость» одного блока — порядка 10–15 наносекунд! Подобный результат достигнут и на Fedora 22 . Вот график замеров (по оси x — количество блоков, по y — наносекунд на блок):
Кроме того, видно, что зависимость линейная — количество блоков не влияет на временную характеристику.
Профилирование
Получение и анализ результатов происходит в программе с незамысловатым названием profiler_gui (в директории bin). Инициализация профилоровщика возможна двумя способами:
- Подключением по сокету приложением profiler_gui. Для этого необходимо инициализировать прослушивание сокета в профилируемом приложении. Это делается просто:
profiler::startListen();
Данная функция запускает поток, который слушает по порту28077
(порт можно поменять параметром в функцииprofiler::startListen(portNumber)
) команды управления. Остановить прослушивание можно вызовом функции (хотя это совсем не обязательно):profiler::stopListen();
Сбор блоков начинается после коннекта profiler_gui к профилируемому приложению и нажатия на кнопку «Capture» на тул-баре. После остановки профилирования (нажать на «Stop») собранная информация передается через сокет из профилируемого приложения в profiler_gui и сразу же сохраняется на диск в файл easy_profiler.cache. Можно также сохранить всю информацию в отдельный файл (при этом просто происходит перемещение файла easy_profiler.cache).
- Сохранением результата в файл. Для этого сперва необходимо инициализировать профайлер, а затем в необходимый момент сохранить файл. Это делается следующим образом:
int main() { EASY_PROFILER_ENABLE; /* do work*/ profiler::dumpBlocksToFile("test_profile.prof"); }
После этого сохранённые файлы можно открыть в программе profiler_gui
Для получения информации о переключении контекста в Windows необходимо запускать профилируемое приложение с правами администратора. В linux дело обстоит чуть сложнее: необходимо запускать с привилегиями суперпользователя скрипт, находящийся в директории
scripts/context_switch_logger.stp
с параметрами. Данный скрипт интерпретируется программой systemtap. В Fedora нужно выполнить команду: #stap -o /tmp/cs_profiling_info.log scripts/context_switch_logger.stp name APPLICATION_NAME
Где
APPLICATION_NAME
— имя профилируемого приложения, файл /tmp/cs_profiling_info.log
— файл, куда записывается информация о переключениях контекста. Привилегии суперпользователя необходимы потому, что информацию о переключении контекста возможно получить только в пространстве ядра.Анализ результатов
Для демонстрации возможностей анализатора результатов попрофилируем простой пример из CryEngine. В самом CryEngine есть несколько профилировщиков и для их организации существуют макросы, в которые легко встроить любой профайлер.
После компиляции запускаем тестовый пример, запускаем программу profiler_gui, коннектимся к приложению (иконка: , рядом с ней можно ввести ip-адрес или имя хоста, на котором запущено профилируемое приложение). После удачного коннекта (иконка немного позеленеет: ) можно запускать сессию профилирования. После нажатия на кнопку начнётся сбор статистики в профилируемом приложении. Для завершения сессии профилирования нужно закрыть появившееся окошко.
На скриншоте представлен общий вид программы с результатом
В верхней части окна представлены запущенные потоки и сохранённые блоки, длительность которых можно оценить по горизонтальный шкале. Вертикально в рамках каждого блока показывается его иерархия.
В центральной части представлена диаграмма времён либо потока, либо выбранного блока. Здесь время исполнения блока оценивается по вертикали, по горизонтали — время выполнения программы. т.е. можно смотреть всплески длительности блоков и при необходимости более детально оценить проблему.
В нижней части представлено дерево выполнения блоков для выбранного участка с подробнейшей статистикой. Здесь можно сортировать по длительности, искать самые долгие блоки, оценивать количество вызовов того или иного блока. Выбор участка осуществляется в верхней части экрана зажатием правой кнопки мыши и выделением необходимого куска.
Краткую статистику по блоку можно посмотреть в верхней части экрана. После наведения курсора на блок — появляется всплывающее окошко с краткой сводкой:
В этой сводке информация по общей длительности суммарно всех блоков такого типа и сколько эта сумма составляет процентов от фрейма (самый верхний родитель для данного блока), от суммарного времени потока и от своего родителя. Во многих случаях это исчерпывающая информация.
Ещё одной очень удобной фичей является динамическое включение/отключение блоков. Для этого надо открыть диалог (иконка ) и в появившемся окне включить или отключить желаемые блоки. При следующей сессии профилирования эти настройки будут учтены.
Отключаем сбор информации для функции C3DEngine::GetWaterLevel
Итак, преимущества профилировщика:
— Скорость работы
— Минимальные затраты памяти
— Кроссплатформенность
— Удобное и функционально графическое представление
Единственным ограничением использования является необходимость сборки профилируемого приложения компилятором, поддерживающим стандарт c++11.
Данный профилировщик будет полезен как для разработчиков движков игр (как ИИ, так и 3D), так и для тех, кто использует уже готовые движки, да и для всех, кто заботится о производительности своего приложения. Данный профайлер используется нами в рамках разработки системы визуализации для авиационных и тактических тренажёров.
Спасибо за внимание! С удовольствием ждём обратной связи (вопросы, пожелания, баги, звёздочки на гитхабе, пулл-реквесты). В процессе разработки были решены кое-какие нестандартные задачи, о чём хочется написать отдельные статьи.
Комментарии (3)
2 февраля 2017 в 11:03
+1↑
↓
Как то сумбурно изложено, как инициализировать профайлер в начале, я так понимаю вызовом EASY_PROFILER_ENABLE;?2 февраля 2017 в 11:07
0↑
↓
Это макрос сразу начинает сессию профилирования. Это удобно для сохранения в дальнейшем в файл. Если подключаться через gui-приложение, то данный макрос необязателен2 февраля 2017 в 11:10
+1↑
↓
Если требуется управлять запуском/остановкой сбора профилируемой информации из самого профилируемого приложения, то да, нужно вызывать EASY_PROFILER_ENABLE, EASY_PROFILER_DISABLE.
Другой вариант — управлять запуском/остановкой из GUI. В этом случае, в профилируемом приложении нужно только вызвать profiler: startListen (), а команды старт/стоп будут приходить из GUI.