Шустрий, зручний і багатоплатформовий профілювальник C++ коду

Всім привіт. Кілька місяців тому ми разом з victorzs вирішили зробити простий і зручний профілювальник c++ коду (мається на увазі профілювання часу виконання ділянок коду, функцій).


Скріншот профілювання прикладу з SDK CryEngine

Існуючі рішення нам не підходили по ряду причин. Нам потрібен був якісний профайлер, вміє робити наступне:
  • Профілювати вибрані ділянки коду
  • Працювати на декількох платформах
  • Враховувати перемикання контексту
  • Вимагати мінімальних додаткових витрат пам'яті під час профілювання
  • Не накладати додаткових обмежень під час виконання програми. Погодьтеся, якщо профілювальник буде працювати довше, ніж профилиуремый косочек коду, то можна зробити неправильні висновки.


В результаті ретельного опрацювання з'явився на світ профайлер, вміє робити все вищеперелічене, і навіть більше!
Якщо ви хочете знати, скільки часу працює ваш код, і мати при цьому об'єктивні докази — прошу під кат, де я покажу, як використовувати профілювальник.


Інтегрування в код

  1. Качаємо і розпаковуємо свіжий реліз звідси: https://github.com/yse/easy_profiler/releases
  2. Прописуємо компілятору директорію для пошуку заголовних файлів:
    <easy_profiler_release_dir>/include
  3. Прописуємо компоновщику директорію для пошуку бібліотек:
    <easy_profiler_release_dir>/bin
  4. Додаємо definition компілятору:
    BUILD_WITH_EASY_PROFILER
  5. Додаємо блоки в ті місця коду, які хочемо заміряти. Наприклад:

    #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"
    }
    

  6. Не забуваємо покласти поруч із зібраним додатком бібліотеку easy_profiler (*.dll або *.so). Чи прописуємо в системну змінну
    PATH
    (в лінуксі достатньо в
    LD_LIBRARY_PATH
    ) директорію
    <easy_profiler_release_dir>/bin


Додані блоки у режимі збору статистики займають мінімально можливий час (як ми цього добилися — в подальших статтях про технічної реалізації). На машині з процесором Core i7-5930K 3.5 GHz, 16 Gb RAM, Win7 Pro в додатку з 12 потоками середня «вартість» одного блоку — близько 10-15 наносекунд! Подібний результат досягнутий і на Fedora 22 . Ось графік вимірів (по осі x — кількість блоків, по y — наносекунд на блок):



Крім того, видно, що залежність лінійна — кількість блоків не впливає на часову характеристику.

Профілювання



Отримання та аналіз результатів відбувається в програмі з нехитрою назвою profiler_gui (у теці bin). Ініціалізація профилоровщика можлива двома способами:
  1. Підключенням по сокету додатком profiler_gui. Для цього необхідно ініціалізувати прослуховування сокета профилируемом додатку. Це робиться просто:
    profiler::startListen();
    

    Дана функція запускає потік, який слухає по порту
    28077
    (порт можна змінити параметр функції
    profiler::startListen(portNumber)
    ) команди управління. Зупинити прослуховування можна викликом функції (хоча це зовсім не обов'язково):
    profiler::stopListen();
    


    Збір блоків починається після конекту profiler_gui до профилируемому додатком і натискання на кнопку «Capture» на тул-барі. Після зупинки профілювання (натиснути «Stop») зібрана інформація передається через сокет з профільованого програми в profiler_gui і відразу ж зберігається на диск у файл easy_profiler.cache. Можна також зберегти всю інформацію в окремий файл (при цьому відбувається переміщення файлу easy_profiler.cache).

  2. Збереженням результату у файл. Для цього спершу необхідно ініціалізувати профайлер, а потім у потрібний момент зберегти файл. Це робиться наступним чином:
    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), так і для тих, хто використовує вже готові движки, так і для всіх, хто піклується про продуктивності програми. Даний профайлер використовується нами в рамках розробки системи візуалізації для авіаційних і тактичних тренажерів.

Спасибі за увагу! З задоволенням чекаємо зворотного зв'язку (питання, побажання, баги, зірочки на гітхабі, пулл-реквесты). У процесі розробки були вирішені деякі нестандартні завдання, про що хочеться написати окремі статті.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.