Профилирование C++ кода на Linux: как найти узкие места и ускорить программу?
Лучший способ профилирования C++ на Linux: perf + FlameGraph
Ваша проблема типична: вы написали код, который работает, но неэффективно. Профилирование поможет найти узкие места. Я рекомендую использовать perf — это встроенный в ядро Linux инструмент с минимальными накладными расходами. Он работает на уровне выборки (sampling), не замедляя программу.
Шаг 1: Установка и компиляция с отладочными символами
Установите perf и соберите программу с флагами -g -O2 (флаг -g добавляет отладочные символы для читаемых стеков):
sudo apt install linux-tools-common linux-tools-generic
g++ -g -O2 -march=native -o myapp main.cpp
Шаг 2: Запись профиля
Запустите программу под perf с частотой выборки 99 Гц (стандартная частота, избегающая синхронизации с системным таймером):
sudo perf record -F 99 -g ./myapp
# После завершения появится файл perf.data
Шаг 3: Просмотр отчёта
Интерактивный просмотр:
perf report -g graph
Вы увидите таблицу с функциями, отсортированными по проценту времени. Нажмите Enter на функции, чтобы развернуть стек вызовов. Скорее всего, вы увидите, что std::sin и std::cos занимают 80-90% времени — это ожидаемо из-за вложенного цикла.
Шаг 4: Визуализация с FlameGraph
Для наглядности сгенерируйте флейм-график (огненную диаграмму):
# Скачайте скрипты
git clone https://github.com/brendangregg/FlameGraph
cd FlameGraph
# Преобразуйте perf.data в стектрейсы
sudo perf script > out.perf
# Создайте SVG
./stackcollapse-perf.pl out.perf > out.folded
./flamegraph.pl out.folded > profile.svg
Откройте profile.svg в браузере. Ширина каждого прямоугольника пропорциональна времени, проведённому в функции. Кликнув на прямоугольник, вы увидите его долю и контекст вызова.
Что делать с результатами?
В вашем случае очевидное узкое место — вложенный цикл из 1000 итераций с вызовом тригонометрических функций. Решения:
- Оптимизация алгоритма: пересмотрите необходимость 1000 итераций. Возможно, достаточно 10-20.
- Таблицы предвычислений (lookup tables): если аргументы повторяются, вычислите sin/cos один раз.
- Использование SIMD: библиотеки вроде Intel MKL или Agner Fog's vector class могут ускорить вычисления.
- Упрощение: замените
sin(val*1.5)+cos(val*0.7)на аппроксимацию полиномами.
Альтернативы perf
Если вам нужен простой старт, попробуйте Valgrind --tool=callgrind. Он эмулирует каждую инструкцию, давая точные данные, но замедляет программу в 10-50 раз. Для 4-минутного выполнения это неприемлемо — perf здесь лучший выбор.
Также можно использовать gprof, но он требует специальной компиляции (-pg) и даёт менее точные результаты из-за инструментирования вызовов функций. Я рекомендую начать с perf — это промышленный стандарт.
Профилирование с помощью Google Benchmark и микро-бенчмарки
Если вы хотите не только найти узкие места, но и проверить гипотезы об ускорении, используйте библиотеку Google Benchmark. Она позволяет изолировать отдельные функции и замерять их производительность с высокой точностью.
Пример для вашего кода:
#include <benchmark/benchmark.h>
#include <vector>
#include <cmath>
static void BM_ProcessData(benchmark::State& state) {
std::vector<double> input(10000);
for (auto& v : input) v = 1.5;
for (auto _ : state) {
auto result = processData(input); // ваша функция
benchmark::DoNotOptimize(result);
}
}
BENCHMARK(BM_ProcessData);
BENCHMARK_MAIN();
Скомпилируйте с -lbenchmark -lpthread и запустите. Вы получите время выполнения в микросекундах, количество итераций и статистику. Затем можно модифицировать функцию (например, уменьшить количество итераций цикла) и сравнить результаты.
Google Benchmark полезен для A/B-тестирования оптимизаций. Он автоматически подбирает количество итераций для стабильных замеров и сообщает о выбросах. Это дополнительный инструмент к perf: сначала perf показывает общую картину, потом Google Benchmark детально проверяет конкретные функции.
Установка: sudo apt install libbenchmark-dev или соберите из исходников с GitHub.