Введение: почему важно профилировать C++ код?
C++ — один из самых быстрых языков программирования, но даже на нём можно написать медленный код. Часто разработчики тратят часы на оптимизацию не тех участков программы, руководствуясь интуицией. Профилирование — это научный подход к оптимизации: вы измеряете, где программа тратит больше всего времени, и целенаправленно улучшаете эти места.
Для начинающих C++ программистов профилирование может казаться сложным, но на самом деле базовые инструменты осваиваются за один вечер. В этой статье мы разберём три популярных инструмента: gprof, perf и Valgrind, и научимся интерпретировать их результаты.
Что такое профилирование и какие бывают методы?
Профилирование — это сбор данных о работе программы во время её выполнения. Существует два основных подхода:
- Инструментирующее профилирование — в код программы добавляются специальные "зонды" (например,
gprof). Минус: замедляет выполнение программы. - Сэмплирующее профилирование — через равные промежутки времени "заглядывает" в программу и записывает, какая функция сейчас выполняется (например,
perf). Почти не замедляет программу.
Ещё есть инструменты для поиска утечек памяти (Valgrind) и анализа кэша. Для начала достаточно освоить gprof и perf.
Инструмент 1: gprof — классика профилирования
gprof — встроенный профилировщик для GCC. Он работает в два этапа: сбор данных и их анализ.
Как использовать gprof
Сначала скомпилируйте программу с флагом -pg:
g++ -pg -o my_program my_program.cpp
Запустите программу:
./my_program
После выполнения появится файл gmon.out. Теперь запустите gprof:
gprof my_program gmon.out > report.txt
Откройте report.txt и посмотрите на таблицу:
Flat profile:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ms/call ms/call name
60.00 0.06 0.06 10000 0.01 0.01 slow_function
30.00 0.09 0.03 5000 0.01 0.02 medium_function
10.00 0.10 0.01 1 10.00 10.00 main
В колонке % time показано, сколько процентов времени заняла функция. В нашем примере slow_function — главный кандидат на оптимизацию.
Совет: Для небольших программ gprof отлично подходит. Для больших проектов он может давать неточные данные из-за оптимизаций компилятора.
Инструмент 2: perf — мощный сэмплер для Linux
perf — это профилировщик, встроенный в ядро Linux. Он работает методом сэмплирования и почти не замедляет программу. Идеален для production-систем.
Базовое использование perf
Скомпилируйте программу с отладочными символами (флаг -g):
g++ -g -o my_program my_program.cpp
Запустите профилирование:
perf record ./my_program
После завершения работы программы, посмотрите отчёт:
perf report
Вы увидите интерактивный интерфейс. Нажав на функцию, можно увидеть, какие строки кода выполнялись чаще всего. Пример вывода:
Samples: 1K of event 'cycles'
Event count (approx.): 250000000
Overhead Command Shared Object Symbol
45.32% my_program my_program [.] slow_function
30.15% my_program my_program [.] medium_function
12.50% my_program libc-2.31.so [.] malloc
8.03% my_program my_program [.] main
Важно: perf требует прав root для сбора данных о ядре, но для пользовательских программ можно запускать без sudo.
Продвинутые команды perf
perf stat ./my_program— показывает количество инструкций, промахов кэша, ветвлений и другую статистику.perf top— показывает "горячие" функции в реальном времени (аналогtop, но для кода).perf annotate— показывает ассемблерный код с аннотацией, какие инструкции выполняются чаще всего.
Инструмент 3: Valgrind — профилирование и поиск утечек
Valgrind — это не просто профилировщик, а целый набор инструментов. Самый популярный — Callgrind (профилирование) и Massif (анализ памяти).
Профилирование с Callgrind
Запустите программу под Valgrind:
valgrind --tool=callgrind ./my_program
После выполнения появится файл callgrind.out.XXXX. Чтобы его прочитать, используйте callgrind_annotate:
callgrind_annotate callgrind.out.XXXX
Вывод показывает количество инструкций, выполненных каждой функцией:
--------------------------------------------------------------------------------
Ir file:function
--------------------------------------------------------------------------------
1,200,000,000 ???:slow_function [/home/user/my_program]
800,000,000 ???:medium_function [/home/user/my_program]
200,000,000 ???:main [/home/user/my_program]
Число Ir — это количество выполненных инструкций. Чем оно больше, тем "тяжелее" функция.
Анализ памяти с Massif
Massif показывает, сколько памяти выделяет программа в каждый момент времени:
valgrind --tool=massif ./my_program
ms_print massif.out.XXXX
Вы увидите график использования памяти и таблицу, в какой функции