Скриншот профилирования примера из SDK CryEngine
Существующие решения нам не подходили по ряду причин. Нам нужен был качественный профайлер, умеющий делать следующее:
- Профилировать выбранные участки кода
- Работать на нескольких платформах
- Учитывать переключение контекста
- Требовать минимальных дополнительных затрат памяти во время профилирования
- Не накладывать дополнительных временных ограничений во время выполнения приложения. Согласитесь, если профилировщик будет работать дольше, чем профилиуремый кусочек кода, то можно сделать некорректные выводы.
В результате тщательной проработки появился на свет профайлер, умеющий делать всё вышеперечисленное, и даже больше!
Если вы хотите знать, сколько времени работает ваш код, и иметь при этом объективные доказательства — прошу под кат, где я покажу, как использовать профилировщик.
Интегрирование в код
- Качаем и распаковываем свежий релиз отсюда: https://github.com/yse/easy_profiler/releases
- Если вы испольузете
CMake
для сборки, то
- Определите переменную
CMAKE_PREFIX_PATH
, которая указывает на директорию<easy_profiler_release_dir>/cmake/easy_profiler
из релиза - Используйте
find_package(easy_profiler REQUIRED)
иtarget_link_libraries(... easy_profiler)
В ином случае:
- Прописываем компилятору директорию для поиска заголовочных файлов:
<easy_profiler_release_dir>/include
- Прописываем компоновщику директорию для поиска библиотек:
<easy_profiler_release_dir>/bin
- Определите переменную
- Добавляем definition компилятору:
BUILD_WITH_EASY_PROFILER
- Добавляем блоки в те места кода, которые хотим замерить. Например:
#include <easy/profiler.h> 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
) директорию<easy_profiler_release_dir>/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), так и для тех, кто использует уже готовые движки, да и для всех, кто заботится о производительности своего приложения. Данный профайлер используется нами в рамках разработки системы визуализации для авиационных и тактических тренажёров.
Лицензия либо Apache 2.0, либо GPL v.3 — как на либину, так и на gui. Используйте любую из этих лицензий.
Спасибо за внимание! С удовольствием ждём обратной связи (вопросы, пожелания,
Комментарии (36)
lookid
02.02.2017 11:32EASY_BLOCK("Calculating multiplication", profiler::colors::Blue500);
Ох длиннющая же запись. Предеться обкладывать дефайнами или инлайнами. Пишешь код в 30 символов, а профайлер в 130.
victorzs
02.02.2017 12:57+2Цвет задавать не обязательно (так просто нагляднее), а для неймспейсов можно завести более короткие алиасы (на крайний случай using namespace).
Цвета имеют формат uint32_t (ARGB) и использование profiler::colors никому не навызявается — вы можете сформировать свой набор цветов или задавать цвет напрямую (например, 0xff2196f3 вместо profiler::colors::Blue500).
Ну а длинное или короткое имя задать блоку — это ваш выбор.
iSage
02.02.2017 13:08+21. >Кроссплатформенный
Я, конечно, собирать не пробовал, но в описании только lib/win. Мака нет.
2. Что с лицензией? Что под апаче, что под gpl?
3. Минусы: требует правки кода, умеет только именованные участки, не умеет показывать узкие места ( в отличии от perf, xcode и того, что там в студии)yse
02.02.2017 13:14Мака нет.
К сожалению под рукой мака нет, но clang`ом вроде собирается, используется Qt. Так что судя по всему под маком тоже взлетит без проблем. Если есть у кого собрать и проверить под маком — будем рады
Что с лицензией?
Используйте либо апач, либо GPL. И gui и либина под этими лицензиями. Обновлю информацию сразу в статью. Спасибо!
Минусы: требует правки кода...
Я бы не стал говорить, что это прям минус, неудобно, да, но практика показывает, что иногда наоборот кусочно полезно профилировать.
victorzs
02.02.2017 16:53+1По 3 пункту: я могу ошибаться, но вроде бы профайлеры, которые собирают информацию "на лету" позволят вам увидеть информацию по приложению в целом, а не по каждому фрейму в отдельности? И это безусловно полезно и во многих случаях большего и не нужно.
Наш профайлер позволит вам увидеть что происходило в каждом конкретном фрейме. Это может быть полезно, если нужно обнаружить причину тормозов в конкретный момент времени.
Для полноты картины я бы использовал оба типа профайлеров.victorzs
02.02.2017 17:41+3Прошу прощения, я действительно ошибся. Вспомнил, что в том же VTune можно выделить интересующий участок на графике.
mkarev
02.02.2017 21:14+2Для полноты картины я бы использовал оба типа профайлеров.
У нас как раз такая практика — "настоящим" профайлером ищем узкие места, а потом обкладываем их в коде замерами, подобными сабжевым, и вперед оптимизировать.
Получаем преимущества обоих методов
- точная локализация узких мест инструментальным профилированием (xcode, vtune, etc)
- быстрые и повторяемые замеры ручным профилированием во время решения найденных проблем
Alexey2005
02.02.2017 16:14+2Лучший из известных мне профилировщиков — Intel VTune. Профилировщик от AMD (CodeAnalyst) уже значительно хуже, зато бесплатный. Оба они работают и в Windows, и в Linux. Все остальные известные мне профилировщики по возможностям вообще не сравнимы с этими двумя.
lieff
02.02.2017 21:56А самое главное — VTune один из немногих использует хардварные возможности. У интеля есть либа с исходниками на гитхабе, дающая доступ к этим хардварным перформанс счетчикам, это самый точный способ на интеле. По идее эти хардварные каунтеры можно прикрутить и к этому профилировщику, добавить sampling профайл и не надо будет менять код вообще, нужна только отладочная инфа. Только под виндой ему нужен драйвер для доступа к MSR регистрам, потому в винде в том числе и VTune иногда ломается, в лине работает из коробки без драйвера.
mkarev
03.02.2017 07:03XCode Instruments умеет считать HW счетчики производительности.
Можно выбрать из большого списка, что интересует, например кэш промахи по данным L1.
Правда кол-во одновременно наблюдаемых счетчиков ограничено, но я думаю это ограничение конкретного CPU.
Правда на ARM, к сожалению, performance counters не доступны, но с другой стороны v-tune эту архитектуру вообще не поддерживает.
XCode Instruments не зависит от исходного кода, ему нужен бинарник и файл с символами.
lieff
03.02.2017 10:51Да, XCode тоже поддерживает, в либе поддержка macos кстати тоже есть, и тоже нужен MacMSRDriver.
Так что теоретически можно сделать аля кроссплатформенный VTune отпенсорсный.
0xd34df00d
03.02.2017 19:19в лине работает из коробки без драйвера.
Нет, без драйвера работает только basic hotspots (по крайней мере, из того, что я пробовал). Для всяких там advanced hotspots, general exploration и уж тем более архитектурно-специфичных анализов также нужны модули ядра.
lieff
03.02.2017 21:17Я про либу с гитхаба, как и perf отдельные модули ядра им не нужны, все уже есть в ядре давным давно (конфиг CONFIG_HW_PERF_EVENTS, отключенным его не видел). Ими можно пользоваться и вообще без софта из скриптов через /sys/, но не все возможности, а так же в своей программе через libperf.
elderorb
02.02.2017 19:33+4Раз уж на Qt, как насчёт плагина для QtCreator-а? :)
yse
03.02.2017 14:01+1Вы имеете в виду gui проинтегрировать в QtCreator? Любопытная мысль, что инструмент под рукой.
Были идеи плагина, который позволял бы автоматически блоки проставлять в функции, отображал бы цвет блоков, в общем плагин для кода. А вот гуи добавить что-то мыслей не возникло =( Спасибо за подсказку!lookid
03.02.2017 14:47+1Можно сделать плагин, который ставит на полях метки для профилеровщика. Чтобы руками не писать. Или в контектное меню добавить.
victorzs
08.02.2017 13:22Мы, к сожалению, опыта написания плагинов для QtCreator-а не имеем. Если есть желание, можете включиться в разработку и создать плагин. У Вас, скорее всего, это получится лучше, а мы, если потребуется, проконсультируем по особенностям работы профайлера / гуи :)
oYASo
03.02.2017 04:48+1Выглядит клево! Попробуем, спасибо!
yse
03.02.2017 14:11Спасибо! Ждём обратной связи! =)
oYASo
03.02.2017 17:37+1О чем хочется сразу сказать — поправьте нормально файлы сборки. Дело в том, что, во-первых, при указании cmake папки сборки, финальные исполняемые файлы все равно зачем-то копируются в каталог сорцов. Портить папку с сорцами, особенно когда я указал этого не делать — плохой тон.
Во-вторых, хочется все это проинсталлировать в систему (Linux), а не собирать все это в отдельную папку, потом прописать пути и т.д. Инсталляции пока нет.
В-третьих, раз уж используется cmake, хочется в своем проекте просто написать find_package(easy_profiler) и получить сразу пути к хедерам и библиотекам, а также все необходимые дефайны. А чтобы руками не писать модуль FindEasyProfiler.cmake, рекомендую почитать про package layout cmake. То есть это позволит проинсталлировать в систему вашу библиотеку сразу с автоматически сгенерированным конфигом cmake со всеми правильными путями, клиенту нужно будет написать в своем коде только find_package(easy_profiler).
Есть еще вопросы конкретно по использованию, но я их в личку напишу.victorzs
03.02.2017 18:42+1Спасибо за ценные замечания! Думаю, в ближайшие дни сможем уделить время системе сборки.
Вопросы по использованию можете также дублировать мне.
yrouban
04.02.2017 15:21+1Молодцы, хороший прибор сделали. Насколько я понимаю, GUI часть намного сложнее чем сборщик :)
Вдохновение пришло, наверное, от Event Tracing for Windows API + GPUView.
Есть ещё аналогичный подход у Гугла — systrace (https://developer.android.com/studio/profile/systrace.html)
Intel PCM, про который тут упоминали, просто меряет hardware performace counters без привязки к коду. Это совсем другой подход. Раз уж тут упомянули Vtune и CodeXL, то было бы очень круто сделать из вашего профилировщика плагин для CodeXL. Ещё крутой фичей было бы профилирование чужого кода (то есть без возможности перекомпиляции), например замерять время вызова некоторых функций или участков кода. Это можно сделать с помощью инструментирования бинарного кода (см. pintool).
Я с вами согласен в том, что часто бывает проще сделать свой прибор на известной технологии, чем изучать чужие приборы и подходы и адаптировать их под свои проекты.victorzs
09.02.2017 14:08Спасибо :)
Насчет GUI vs core — даже не знаю как оценить что сложнее, и там и там есть свои хитрые особенности.
А вдохновлялись скорее проектом Brofiler :) см. комментарий ниже по тексту.
По поводу профилирования без встраивания в код — пока особо не разбирались в этом вопросе (и в кросс-платформенном варианте здесь, я думаю, все очень непросто), решили начать с простого.
yse
07.02.2017 19:02Уважаемые хабравчане, огромное спасибо за конструктивную обратную связь. Это действительно мотивирует!
Отдельное спасибо oYASo за совет об автоматической генерации файлов cmake (посредством package layout cmake). Пользователям данной системы сборки теперь достаточно писатьfind_package(easy_profiler)
. Это действительно удобно. Обновил информацию в статье.oYASo
08.02.2017 01:35+2Сейчас хорошо, но я все-таки предлагаю довести до отлично :)
Дело в том, что в коде сборки вы переопределяете CMAKE_INSTALL_PREFIX на папку в директории с сорцами sdk. Если ее оставить пустой, то по умолчанию на никсах она определена как /usr/local. Вам остается просто написать, что инсталлировать хедеры нужно в include/easy_profiler, либки в lib/, исполняемые файлы в bin/, файлы со скриптами можно кинуть в share/. А фишка в том, что cmake по умолчанию ищет свои конфигурационные файлы в /usr/local/lib/cmake. Это значит, что если вы просто на никсах будете устанавливать все в папки по умолчанию, то прям из коробки можно будет писать
cmake…
make
sudo make install
и дальше в коде, без CMAKE_PREFIX_PATH, сразу писать
find_package(easy_profiler).
На винде, увы, это не прокатит, потому что там нет логики рассовывания библиотек по системе, поэтому CMAKE_PREFIX_PATH писать придется.
Далее, есть еще такое предложение. Везде в коде вы пишите add_definitions и include_directories. На самом деле, это штука устаревшая, и работает локально. Намного удобнее использовать target_compile_definitions и target_include_directories, которым можно указывать дополнительно PUBLIC, PRIVATE, INTERFACE в зависимости от того, нужно ли экспортировать эти директивы и хедеры в проекты, которые подключают ваш проект. В итоге, если вы определите target_compile_definitions(easy_profiler PUBLIC BUILD_WITH_EASY_PROFILER), то все проекты, которые будут указывать в target_link_libraries вашу библиотеку, автоматически подключает все определения с модификатором PUBLIC. Тоже самое касается и хедеров, если проделать аналогичное с target_include_directories.victorzs
08.02.2017 08:34Да, давненько мы в cmake не заглядывали. Вот так пользуешься старьем, оно работает и не знаешь про такие полезные конструкции :)
Спасибо :)
Darkxiv
09.02.2017 13:39Странно, что ещё никто не упомянул brofiler. Вы случайно не им вдохновлялись?
victorzs
09.02.2017 13:51+1Вы совершенно правы :)
Однако он Windows-only, а нам нужно было профилировать и на Linux. Да, у него есть pull-request с портом под Linux, но он довольно старый и так и не влит. Ну и плюс некоторые вещи для себя более удобно сделали.
AndreySu
Как то сумбурно изложено, как инициализировать профайлер в начале, я так понимаю вызовом EASY_PROFILER_ENABLE;?
yse
Это макрос сразу начинает сессию профилирования. Это удобно для сохранения в дальнейшем в файл. Если подключаться через gui-приложение, то данный макрос необязателен
victorzs
Если требуется управлять запуском/остановкой сбора профилируемой информации из самого профилируемого приложения, то да, нужно вызывать EASY_PROFILER_ENABLE, EASY_PROFILER_DISABLE.
Другой вариант — управлять запуском/остановкой из GUI. В этом случае, в профилируемом приложении нужно только вызвать profiler::startListen(), а команды старт/стоп будут приходить из GUI.
AndreySu
Возможно ли рисовать в риалтайме графики профилирования, как, например, это сделано в профилировщике Visual Studio? В GUI.
yse
Сами графики в реалтайме не идут. Но это можно предусмотреть на будущее. По опыту решения наших задач вполне достаточно профилировать сессиями.