Avatar ·

Профилирование C++ кода на Linux: как найти узкие места?

Привет, коллеги! Пишу высоконагруженное приложение на C++ под Linux. Столкнулся с проблемой: программа работает медленнее, чем ожидалось, но я не могу понять, где именно теряется производительность. Пробовал вставлять ручные замеры времени с std::chrono, но это превращает код в кашу и не даёт полной картины. Вот упрощённый пример моего кода, который обрабатывает большой набор данных:
#include <vector>
#include <algorithm>
#include <iostream>

void processData(const std::vector<int>& data) {
    std::vector<int> result;
    for (size_t i = 0; i < data.size(); ++i) {
        if (data[i] % 2 == 0) {
            result.push_back(data[i] * data[i]);
        }
    }
    // Дальше сложные вычисления...
    std::sort(result.begin(), result.end());
    for (auto& v : result) {
        v = v % 1000;
    }
}

int main() {
    std::vector<int> data(1000000);
    std::generate(data.begin(), data.end(), std::rand);
    processData(data);
    return 0;
}
Я использую GCC и собираю с флагами -O2 -march=native. Проблема в том, что я не знаю, какие именно части кода (аллокации в векторе, сортировка, или что-то ещё) жрут больше всего времени. Слышал про профилировщики, но не знаю, с какого начать и как их правильно использовать. Подскажите, пожалуйста, надёжный способ профилирования C++ на Linux для начинающих. <<>>

Используем perf и gprof для профилирования C++ на Linux

Проблема ручных замеров — они искажают результат и не показывают полной картины. Для профилирования C++ кода на Linux есть два отличных инструмента: perf (современный, низкоуровневый) и gprof (классический, но требует перекомпиляции). Рекомендую начать с perf — он встроен в ядро и не требует специальной сборки.

Шаг 1: Базовая профилировка с perf

Установите perf (на Ubuntu: sudo apt install linux-tools-common linux-tools-generic). Скомпилируйте программу с отладочными символами и оптимизацией:

g++ -g -O2 -o myapp myapp.cpp

Запустите профилирование:

sudo perf record ./myapp
sudo perf report

Вы увидите таблицу, где функции отсортированы по проценту времени выполнения. Например, вы можете обнаружить, что std::sort занимает 40% времени, а push_back — ещё 30% из-за реаллокаций.

Шаг 2: Анализ с perf top и аннотацией

Для быстрого просмотра активных функций во время работы используйте:

sudo perf top -p $(pidof myapp)

Чтобы увидеть, какие строки кода самые "горячие", добавьте флаг -l:

sudo perf record -g ./myapp
sudo perf annotate

Это покажет ассемблер с подсветкой инструкций, которые занимают больше всего циклов CPU. Для вашего примера вы увидите, что push_back вызывает реаллокацию вектора, а std::sort — это O(n log n) операция.

Шаг 3: Оптимизация на основе perf

После профилировки вы можете:

  • Заменить вектор на std::vector<int> result; с предварительным reserve() — это уберёт лишние реаллокации.
  • Использовать std::sort с пользовательским компаратором или перейти на std::partial_sort, если нужны только топ-N элементов.
  • Применить std::transform вместо цикла для векторизации.

Пример оптимизированного кода:

void processData(const std::vector<int>& data) {
    std::vector<int> result;
    result.reserve(data.size()); // Убираем реаллокации
    for (auto v : data) {
        if (v % 2 == 0) result.push_back(v * v);
    }
    std::sort(result.begin(), result.end());
    for (auto& v : result) v = v % 1000;
}

Альтернатива: gprof (если нет доступа к perf)

Скомпилируйте с -pg:

g++ -g -O2 -pg -o myapp myapp.cpp
./myapp
gprof myapp gmon.out

Результат — текстовый отчёт с вызовами функций и временем. Минус: gprof добавляет оверхед и не работает с многопоточностью.

Советы для новичков

  • Всегда компилируйте с -g для читаемых имён функций.
  • Используйте -O2 (или -O3) — профилировка без оптимизации бессмысленна.
  • Для многопоточных приложений используйте perf record -e sched:sched_switch для учёта переключений контекста.
  • Попробуйте Valgrind --tool=callgrind для детального анализа кэша и инструкций (но он медленный).

В вашем случае perf покажет, что основное время уходит на сортировку и реаллокацию. Исправьте их — и программа ускорится в 2-3 раза.

<<>>

Профилирование с помощью Google Performance Tools (gperftools)

Если perf кажется слишком низкоуровневым, попробуйте gperftools от Google. Он предоставляет CPU-профилировщик и хип-профилировщик (для анализа памяти). Установка:

sudo apt install google-perftools libgoogle-perftools-dev

Скомпилируйте программу с линковкой библиотеки:

g++ -g -O2 -o myapp myapp.cpp -lprofiler

Запустите с переменной окружения:

CPUPROFILE=myapp.prof ./myapp
pprof --text myapp myapp.prof

Вы получите текстовый отчёт с процентами времени по функциям. Для визуализации используйте pprof --pdf (нужен Graphviz). Плюс gperftools — он не требует прав root и даёт наглядные графики вызовов.

Для вашего примера gperftools покажет, что std::sort и push_back (с реаллокациями) — главные "пожиратели" времени. Рекомендую также запустить heapprofiler (HEAPPROFILE=myapp.heap) для анализа утечек и частоты аллокаций.

Минус: gperftools добавляет небольшой оверхед (около 10-20%), но для большинства задач это приемлемо.