perf (он же perf_events) - штатный профайлер Linux, встроенный прямо в ядро. Это не сторонняя утилита, а интерфейс к аппаратным счётчикам процессора (PMU) и к точкам трассировки ядра. За это его называют швейцарским ножом производительности: один бинарник умеет считать инструкции CPU, ловить промахи кэша, строить профиль со стеками вызовов, анализировать планировщик и даже работать как strace. В этом уроке разберём perf linux по-человечески: с чего начать новичку и как читать вывод, а не просто запускать команды наугад. Актуально на 2026: perf по-прежнему базовый инструмент, но рядом с ним вырос зрелый стек eBPF (bpftrace, bcc), про связку с которым тоже скажем.
Как это устроено: сэмплирование против трассировки
Сначала про два режима, иначе дальше всё будет путаницей.
Счётчики (counting). Процессор имеет физические регистры PMU (Performance Monitoring Unit), которые молча тикают: "выполнено столько-то инструкций", "столько-то промахов кэша". perf просто читает эти числа до и после - почти без накладных расходов. Это режим perf stat.
Сэмплирование (sampling). perf периодически (например, 99 раз в секунду) прерывает программу и записывает, на какой инструкции и в какой функции она была в этот момент. Набрались тысячи замеров - получаем статистику: где код проводит время. Это perf record и perf top. Чем выше частота -F, тем точнее картина, но тем больше overhead.
Главное, что нужно усвоить новичку: perf НЕ замедляет программу в разы, как иногда думают про профайлеры. Накладные расходы низкие - единицы процентов, потому что считает аппаратура CPU, а не эмуляция. Поэтому perf можно запускать даже на боевом сервере, аккуратно и недолго.

Установка и права (актуально на 2026)
perf живёт в пакете linux-tools, привязанном к версии ядра.
Код: Выделить всё
# Ubuntu/Debian
sudo apt install linux-tools-common linux-tools-$(uname -r)
# RHEL/Fedora/RED OS
sudo dnf install perf
# проверка
perf --version
Про права важная деталь, которая часто стопорит новичков. Доступ к profiling регулирует sysctl kernel.perf_event_paranoid:
- значение 2 (частый дефолт в дистрибутивах) - можно профилировать только свои процессы в user-space, ядро закрыто;
- значение 1 - разрешает профилирование ядра пользователю с нужной capability;
- значение -1 - почти всё разрешено (только для отладки, не на проде).
Код: Выделить всё
sudo setcap cap_perfmon,cap_sys_ptrace,cap_bpf=ep $(command -v perf)
Первый perf stat: анализ крови для CPU
Начинаем с perf stat - это как анализ крови для CPU. Запусти любую команду под ним:
Код: Выделить всё
sudo perf stat ls /usr
Код: Выделить всё
4,52 msec task-clock # 0,891 CPUs utilized
3 context-switches # 663,7 /sec
0 cpu-migrations # 0,0 /sec
110 page-faults # 24,3 K/sec
5 421 003 cycles # 1,2 GHz
6 510 870 instructions # 1,20 insn per cycle
1 320 110 branches # 291,8 M/sec
18 400 branch-misses # 1,39% of all branches
- cycles и instructions - сколько тактов CPU и сколько инструкций отработало. Сами по себе цифры скучные, важна их связка.
- insn per cycle (IPC) - сколько инструкций процессор выполняет за один такт. Это пульс программы. Хороший показатель для типичного кода - около 1.0 и выше (современный CPU умеет 2-4). Если IPC упал ниже 0.5 - процессор больше ждёт, чем работает: скорее всего стоит в ожидании памяти. Раньше выводили обратную метрику CPI (такты на инструкцию) - не путайся, это просто 1/IPC.
- branch-misses - доля неверно предсказанных переходов (if, циклы). Норма - доли процента, до 1-2 процентов. Если 5-10 процентов - предсказатель переходов захлёбывается, код плохо предсказуем.
- context-switches - сколько раз ОС переключала процесс. Много переключений (тысячи в секунду на один поток) - намёк на конкуренцию за блокировки или избыток потоков.
- cpu-migrations - перекидывания потока между ядрами. Частые миграции бьют по кэшу, иногда лечатся привязкой к ядру (taskset/affinity).
Код: Выделить всё
sudo perf stat -d ./my_program
perf record и perf report: где именно жжётся CPU
perf stat сказал "процессору плохо". Теперь надо понять, в каком коде. Тут выходит на сцену perf record - он сэмплирует стеки вызовов и пишет их в файл perf.data.
Профилируем живой процесс по PID 10 секунд, со стеками вызовов:
Код: Выделить всё
sudo perf record -F 99 -g -p 12345 -- sleep 10
- -F 99 - частота сэмплирования 99 Гц. Число намеренно нечётное, чтобы не попасть в резонанс с периодическими таймерами в системе и не получить смещённую картину.
- -g - записывать call graph, то есть полный стек вызовов, а не только верхнюю функцию. Без -g увидишь "горит функция X", но не узнаешь, кто её вызвал.
- -p 12345 - цепляемся к процессу. Можно вместо этого -a для всей системы или просто указать команду после --.
Код: Выделить всё
sudo perf record -F 99 --call-graph dwarf -p 12345 -- sleep 10
- --call-graph fp - по указателю кадра. Быстро, почти без overhead, но требует сборки с -fno-omit-frame-pointer, иначе стеки рвутся. Хорошая новость 2026: Ubuntu 24.04+ и Fedora уже пересобрали системные пакеты с frame pointer, так что fp снова стал рабочим по умолчанию на свежих дистрибутивах.
- --call-graph dwarf - размотка по отладочной информации DWARF. Медленнее и тяжелее (perf копирует кусок стека на каждый сэмпл), зато стеки корректные даже на release-сборке без frame pointer. Не уверен в стеках - бери dwarf.
- --call-graph lbr - аппаратный Last Branch Record на Intel Haswell и новее (и на свежих AMD Zen). Быстрый и точный, но ограничен глубиной (порядка 32 записей на старых, до сотен на новых CPU) и собирает в основном user-стек.
Код: Выделить всё
sudo perf report
Код: Выделить всё
# Children Self Command Shared Object Symbol
# ........ ........ ....... ................. ......................
62,15% 0,08% nginx nginx [.] ngx_http_process
48,90% 31,20% nginx libc.so.6 [.] __memcpy_avx
12,03% 12,03% nginx [kernel.kallsyms] [k] copy_user_generic
- Self - сколько CPU съела сама эта функция, без вложенных вызовов. Ищешь горячий код - смотри на Self.
- Children - сколько съела функция вместе со всеми, кого она вызвала. Высокий Children при низком Self значит "сама функция дешёвая, но тянет за собой дорогих детей". У main почти всегда Children близок к 100 процентам - это нормально и ни о чём не говорит.
- Shared Object - где живёт функция: твой бинарник, библиотека (libc.so.6) или ядро. Метка [.] - пользовательский код, [k] - код ядра.
- Symbol - имя функции.
Если вместо имён функций ты видишь голые адреса вроде 0x00007f3a12, значит нет символов. Поставь debuginfo:
Код: Выделить всё
# RHEL/Fedora/RED OS
sudo dnf debuginfo-install nginx
# Debian/Ubuntu - пакеты с суффиксом -dbgsym (нужен репозиторий ddebs)
sudo apt install nginx-dbgsym
perf top, perf trace и братья по цеху
Иногда не хочется писать файл - хочется смотреть в реальном времени, что горит прямо сейчас. Для этого perf top, живой профайлер linux:
Код: Выделить всё
sudo perf top
Чтобы узнать, какие вообще события умеет твой процессор и ядро:
Код: Выделить всё
perf list
Отдельная фишка - perf trace. Работает почти как strace, показывает системные вызовы, но через perf_events, поэтому накладные расходы заметно ниже, чем у strace с его ptrace-ловушками:
Код: Выделить всё
sudo perf trace -p 12345
Всё, что мы делали выше, - это on-CPU профилирование: ищем, где код жжёт процессор, пока реально работает. Но бывает обратная беда: IPC высокий, CPU не загружен, а сервис всё равно медленный - значит код спит в ожидании диска, сети или блокировки. Это off-CPU. У perf для этого есть perf sched:
Код: Выделить всё
sudo perf sched record -- sleep 5
sudo perf sched timehist
perf и eBPF: кто кого в 2026
Зрелость eBPF поменяла расклад, и это надо понимать. perf никуда не делся - он лучший для on-CPU сэмплирования с аппаратными счётчиками и для построения флеймграфов. Но для трассировки конкретных событий (кто открывает файлы, какие запросы медленные, сколько живут TCP-сессии) сегодня удобнее bpftrace и bcc - они программируемые и фильтруют в ядре, отдавая наружу уже агрегат. Грубое правило: считать и сэмплировать CPU - perf; задавать точечные вопросы "кто и почему" - bpftrace. Свежие perf тоже умеют BPF под капотом (например off-CPU профилирование через --off-cpu в новых версиях), так что граница размывается.
perf record даёт сырые стеки. Чтобы превратить тысячи стеков в наглядную картинку, их сворачивают во флеймграф (flame graph) - это уже следующий шаг и следующий урок. Запомни связку: perf record собирает стеки -> из них рисуется флеймграф, где ширина прямоугольника равна доле CPU.
Типичные грабли
- Запуск без прав. perf упирается в kernel.perf_event_paranoid. Проверь cat /proc/sys/kernel/perf_event_paranoid. Значение 2 закрывает ядро. Либо sudo, либо capability CAP_PERFMON через setcap, либо аккуратно sudo sysctl kernel.perf_event_paranoid=1.
- Битые стеки. Видишь обрывки стека на одну строку - бинарник собран без frame pointer, а ты ловишь fp. Перезапиши с --call-graph dwarf.
- Голые адреса вместо имён. Это не баг perf, это отсутствие debuginfo. Ставь -dbgsym / debuginfo-install или собирай свой код с -g.
- Версия пакета не та. perf жёстко завязан на ядро. После обновления ядра доставь linux-tools-$(uname -r), иначе часть фич отвалится.
- Профилируешь не то. Если IPC высокий, а сервис всё равно тормозит, проблема НЕ в CPU - процесс ждёт диск, сеть или блокировку. perf record про on-CPU; off-CPU ожидания ищи через perf sched или offcputime из bcc.
- Слишком высокая -F на проде. -F 999 и выше заметно грузит систему и раздувает perf.data. Для боевого сервера 99 Гц на 10-30 секунд - безопасный режим.
- Поставь perf под своё ядро и проверь perf --version. Посмотри cat /proc/sys/kernel/perf_event_paranoid.
- Запусти sudo perf stat -d -- find / -name "*.conf" 2>/dev/null. Найди в выводе IPC и процент cache-misses, оцени их по правилам из урока.
- Создай нагрузку: yes > /dev/null & и запомни его PID.
- Сними профиль: sudo perf record -F 99 -g -p PID -- sleep 5, затем sudo perf report. Найди функцию с максимальным Self, нажми на ней Annotate.
- Запусти sudo perf top на 10 секунд и поймай вершину списка. Не забудь kill PID после.
- Чем сэмплирование (perf record) отличается от подсчёта счётчиков (perf stat) и когда что применять?
- Что показывает IPC и какое значение должно тебя насторожить? Куда смотреть дальше, если IPC низкий?
- В чём разница между колонками Self и Children в perf report и почему у main всегда большой Children?
- Почему вместо имён функций иногда видны голые адреса и как это лечится? Чем отличаются --call-graph fp и --call-graph dwarf?
perf - штатный профайлер с низким overhead, можно аккуратно крутить даже на проде. Начинай с perf stat (здоровье CPU: IPC, cache-misses, branch-misses), затем perf record -g + perf report, чтобы найти горячую функцию по Self. perf top - тот же профиль в реальном времени, perf trace - лёгкая замена strace, а perf sched - вход в off-CPU. Для читаемых имён нужны символы и debuginfo, для корректных стеков на release-сборках - --call-graph dwarf. В 2026 perf делит сцену с eBPF: считать и сэмплировать - perf, точечные вопросы - bpftrace/bcc. А собранные стеки - прямой путь к флеймграфам, которые разберём дальше.