Профилирование C++ на Linux: гайд для тех, кто хочет ускорить код

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Ваш C++ код работает медленно? Пора заглянуть под капот

Вы написали идеальный, на первый взгляд, код на C++. Компиляция прошла без ошибок, программа запускается... но работает как-то вяло. Знакомо? Рука тянется переписать всё на ассемблере или срочно добавить многопоточность. Не торопитесь. Скорее всего, проблема не в архитектуре, а в паре «горячих» функций, которые пожирают 90% процессорного времени.

В мире Linux у нас есть мощнейший арсенал инструментов для профилирования C++ кода. Это не магия, это наука. Мы не гадаем, а измеряем. В этом руководстве я покажу вам четыре рабочих подхода: от быстрого «взгляда сверху» до микро-бенчмаркинга. Вы узнаете, как профилировать C++ на Linux как настоящий профи.

Почему догадки — зло, а профилирование — добро

Перед тем как нырять в код, запомните золотое правило оптимизации: «Не оптимизируй то, что не измерено». Ваша интуиция — плохой советчик. Часто мы тратим часы на оптимизацию функции, которая вызывается раз в минуту, и игнорируем банальный цикл в 10 миллионов итераций.

Профилирование даёт ответы на три главных вопроса:

  • Где программа тратит больше всего времени?
  • Почему это происходит (неэффективный алгоритм, много операций ввода-вывода, промахи кэша)?
  • Что изменилось после ваших правок?

Сегодня мы разберём четыре инструмента, которые покроют 99% ваших задач. Поехали!

Инструмент №1: gprof — классика жанра

gprof — это старый, добрый, встроенный профилировщик GNU. Он работает в два этапа: сбор статистики во время выполнения программы и её анализ. Плюсы: не нужно ставить ничего лишнего, есть в каждом дистрибутиве. Минусы: даёт приблизительные результаты и не работает с многопоточными программами из коробки.

Как пользоваться gprof

Шаг 1. Компилируем программу со специальным флагом -pg:

g++ -pg -O2 my_program.cpp -o my_program

Обратите внимание: мы используем -O2. Профилировать отладочную сборку без оптимизаций — бессмысленно, вы получите искажённую картину.

Шаг 2. Запускаем программу. Она выполнится немного медленнее, так как gprof собирает данные.

./my_program

Шаг 3. В текущей папке появится файл gmon.out. Анализируем его:

gprof my_program gmon.out > analysis.txt

Открываем analysis.txt и видим что-то вроде:

Flat profile:
Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total
 time   seconds   seconds    calls  us/call  us/call  name
 60.00      0.06     0.06    10000     6.00     6.00  slow_function
 20.00      0.08     0.02     5000     4.00     4.00  fast_function
 10.00      0.09     0.01                             main

Вуаля! Мы видим, что slow_function заняла 60% времени. Это наш кандидат на оптимизацию.

Пример кода для профилирования

Давайте напишем простую программу, которую будем профилировать всеми инструментами:

#include <iostream>
#include <cmath>

void slow_function(int n) {
    volatile double sum = 0.0; // volatile, чтобы компилятор не оптимизировал в ноль
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < 1000; ++j) {
            sum += std::sin(i) * std::cos(j);
        }
    }
    std::cout << sum << std::endl;
}

void fast_function(int n) {
    volatile int count = 0;
    for (int i = 0; i < n; ++i) {
        count += i % 2;
    }
    std::cout << count << std::endl;
}

int main() {
    slow_function(10000);
    fast_function(50000);
    return 0;
}

Скомпилируйте с флагом -pg и запустите gprof. Результат вас удивит: slow_function будет доминировать, хотя fast_function вызывается чаще. Вот она, сила профилирования!

Инструмент №2: perf — мощь ядра Linux

perf — это профилировщик, встроенный прямо в ядро Linux. Он не требует перекомпиляции программы и может анализировать даже системные вызовы. perf — это выбор профессионалов, когда нужно копнуть глубоко.

Установка (если вдруг нет):

sudo apt install linux-tools-common linux-tools-generic

Базовое профилирование CPU

Сначала скомпилируем нашу программу без специальных флагов (только -O2):

g++ -O2 example.cpp -o example

Теперь запускаем perf:

sudo perf record ./example

perf соберёт сэмплы (по умолчанию 4000 раз в секунду). После завершения смотрим отчёт:

sudo perf report

Вы увидите интерактивный интерфейс (можно нажимать стрелки). perf покажет не только ваши функции, но и функции библиотек (например, sin из libm). Это неоценимо, когда вы хотите понять, почему стандартная библиотека медленная.

Совет: используйте флаг --call-graph dwarf, чтобы perf строил графы вызовов:

sudo perf record --call-graph dwarf ./example

Секретный режим: топ самых горячих инструкций

Хотите увидеть, какая строка кода тормозит? Используйте perf annotate:

sudo perf annotate slow_function

perf покажет ассемблерный код вашей функции с процентом времени на каждую инструкцию. Вы увидите, что sin и cos — это настоящие «пожиратели» тактов.

Инструмент №3: Valgrind (Callgrind) — детектив по кэшу

Valgrind — это не один инструмент, а целая лаборатория. Нас интересует Callgrind — симулятор кэша и профилировщик. Он работает медленнее (программа тормозит в 10-20 раз

Блоги

Рекомендаций по книгам