Тема считается "для продвинутых", но на серверах с базами данных, тяжёлой in-memory логикой и двумя сокетами она решает. Начнём с азов и дойдём до perf и eBPF. Всё, что ниже, актуально на 2026 год: на современных дистрибутивах (свежие Debian/Ubuntu, RHEL 9/10, Astra Linux, RED OS) по умолчанию cgroup v2, ядро 6.x, а eBPF уже зрелый и местами честно вытеснил старые подходы.
Откуда берётся частота CPU в Linux и почему она не на максимуме
Современное ядро процессора не работает на одной фиксированной частоте. Оно постоянно её меняет: под нагрузкой разгоняется в turbo (выше базовой), без работы засыпает в энергосберегающие состояния. За это отвечают три вещи: драйвер масштабирования, governor поверх него и idle-состояния.
Драйвер масштабирования частоты. На современных серверах это почти всегда intel_pstate (Intel) или amd-pstate (AMD, активно используется с ядра 5.17+ и зрелый к 2026). Важный нюанс: у intel_pstate в режиме active доступны только два governor - performance и powersave, и его powersave - это не "держим минимальную частоту", а аппаратный аналог ondemand, частота скачет по нагрузке. У amd-pstate в режиме active дефолтный governor - schedutil. Это первое, что путает новичков: "стоит powersave" на Intel не всегда значит "зажали в пол", надо смотреть фактические МГц, а не имя.
Governor (регулятор частоты). Это политика, по которой ядро решает, на какой частоте крутиться:
- performance - держим частоту высокой, экономия не волнует. То, что нужно latency-чувствительному серверу.
- powersave - экономим. На старом acpi-cpufreq это буквально минимальная частота; на intel_pstate active - подстройка под нагрузку. Классика "почему cpu частота в Linux низкая".
- schedutil - современный governor, частоту считает сам планировщик ядра по нагрузке. Дефолт на многих дистрибутивах 2026 и на amd-pstate.
- ondemand/conservative - старые governor для acpi-cpufreq, ещё встречаются на бюджетных и старых машинах.
Тепловой троттлинг (cpu throttling в Linux). Если процессор перегревается (плохое охлаждение, забитый пылью радиатор, тесный корпус, сдохший вентилятор), он принудительно сбрасывает частоту, чтобы не сгореть. Снаружи это выглядит как "сервер ни с того ни с сего просел". Отдельно есть power limit (RAPL): сокет упирается в лимит по ваттам и тоже не разгоняется, даже если по теплу запас есть. Аналогия: бегун в жару переходит на шаг не потому что устал, а чтобы не получить тепловой удар.

turbostat: смотрим реальные частоты, C-states и троттлинг
turbostat - утилита из пакета linux-tools / linux-cpupower (Debian/Ubuntu) или kernel-tools (RHEL/Fedora/Astra/RED OS). Она показывает то, что governor реально делает с железом: фактические частоты по ядрам, residency по C-states и потребление в ваттах через MSR-регистры процессора. Требует root.
Код: Выделить всё
sudo turbostat --interval 5
Код: Выделить всё
Core CPU Avg_MHz Busy% Bzy_MHz TSC_MHz IRQ POLL C1 C6 CoreTmp PkgWatt
- - 1356 42.10 3210 2400 ... 61 78.50
0 0 3431 95.30 3600 2400 ... 63
0 1 148 5.10 2900 2400 ... 63
1 2 3149 88.70 3550 2400 ... 60
- Avg_MHz - средняя частота за весь интервал, включая простой. Считается как реально отработанные такты делить на длину интервала. Низкий Avg_MHz при низком Busy% - это просто простой, не паникуй.
- Busy% - сколько процентов интервала ядро реально считало (было в C0). 95% - ядро пахало почти всё время.
- Bzy_MHz - средняя частота именно в момент работы, без учёта простоя (Avg_MHz делить на Busy%). Вот это ключевая цифра. Если в спеке CPU базовая 2400 и turbo до 3600, а Bzy_MHz под нагрузкой болтается около 2000 - ядро не разгоняется, ищи причину (powersave/минимальная частота или троттлинг).
- TSC_MHz - опорная частота таймера, фиксированная, обычно близка к базовой. Сравнивай Bzy_MHz с ней: Bzy сильно ниже TSC при высоком Busy% - тревога, недоразгон.
- C1/C6 - процент времени в соответствующем idle-состоянии (residency). Высокий C6 на ядре, которое должно работать, - либо ядро реально простаивает, либо latency теряется на выходе из сна.
- CoreTmp - температура ядра в градусах. Под нагрузкой 60-75 это норм, 90+ и близко к Tjmax (часто 100) - почти наверняка тепловой троттлинг.
- PkgWatt - сколько ватт ест весь пакет (сокет). Упёрлось в потолок (power limit/RAPL) - тоже причина не разгоняться.
Если видишь высокий Busy%, но низкий Bzy_MHz - проверь драйвер и governor:
Код: Выделить всё
cpupower frequency-info
Код: Выделить всё
sudo cpupower frequency-set -g performance
Код: Выделить всё
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_driver
NUMA: почему чужая память медленнее и при чём тут numactl
На серверах с двумя и более процессорными сокетами память физически поделена между ними. Это NUMA (Non-Uniform Memory Access). Каждый сокет (нода) имеет "свою" локальную память. Доступ к своей памяти быстрый. Доступ к памяти соседнего сокета идёт через межпроцессорную шину (UPI у Intel, Infinity Fabric у AMD) и медленнее - иногда в полтора-два раза по latency. На больших AMD EPYC NUMA-ноды бывают даже внутри одного сокета (режим NPS), так что NUMA - это не всегда "два физических процессора". Аналогия: своя книга на твоём столе против книги, за которой надо идти к коллеге в другую комнату.
Когда это важно: базы данных (PostgreSQL, MySQL, ClickHouse), Redis с большим датасетом, HPC, JVM с огромной кучей, Kafka. Если процесс крутится на ноде 0, а его память легла на ноду 1 - каждый промах кэша в последнем уровне (LLC) бьёт по дальней памяти, и latency плавает.
Посмотреть топологию нод:
Код: Выделить всё
numactl --hardware
Код: Выделить всё
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 6 7
node 0 size: 32094 MB
node 0 free: 1208 MB
node 1 cpus: 8 9 10 11 12 13 14 15
node 1 size: 32157 MB
node 1 free: 25431 MB
node distances:
node 0 1
0: 10 21
1: 21 10
numastat: ловим промахи нод
numastat показывает счётчики аллокаций памяти по нодам в страницах. Запускаем без аргументов - общесистемная картина:
Код: Выделить всё
numastat
Код: Выделить всё
node0 node1
numa_hit 184523011 90233412
numa_miss 2049883 10422
numa_foreign 10422 2049883
interleave_hit 8801 8790
local_node 183001244 90100021
other_node 1521767 133391
- numa_hit - память успешно выделена на той ноде, которую процесс и предпочитал. Это хорошо, таких должно быть подавляющее большинство.
- numa_miss - память выделена на ЭТОЙ ноде, хотя процесс предпочитал ДРУГУЮ (на той не нашлось свободных страниц). То есть miss считается на ноде-получателе, куда память "приземлилась" вынужденно.
- numa_foreign - зеркало miss: память предназначалась ЭТОЙ ноде, но ушла на другую. Каждому numa_miss на одной ноде соответствует numa_foreign на ноде, которая изначально была предпочтительной (посмотри на цифры выше - 2049883 и 10422 переставлены местами между нодами, это и есть пара miss/foreign).
- interleave_hit - страницы, выделенные по политике interleave (равномерное чередование по нодам) и попавшие на эту ноду как и планировалось.
- local_node - память выделена процессу, который бежал на этой же ноде. Чистая локальность, идеал.
- other_node - память выделена на этой ноде, но процесс в этот момент бежал на ДРУГОЙ ноде. Тоже признак, что планировщик и память разъехались.
Лечится привязкой процесса к ноде через numactl. Запустить так, чтобы и CPU, и память жили на ноде 0:
Код: Выделить всё
numactl --cpunodebind=0 --membind=0 ./my_server
Код: Выделить всё
numastat -p $(pgrep -n postgres)
perf stat: IPC и cache-misses - доказательство деградации
Частоты и NUMA в итоге упираются в одну метрику эффективности - IPC (instructions per cycle, инструкций за такт). Снимаем её через perf stat (пакет linux-tools-common / perf):
Код: Выделить всё
sudo perf stat -a sleep 10
Код: Выделить всё
142819,33 msec task-clock # 14,2 CPUs utilized
9 832 context-switches # 68,8 /sec
412 cpu-migrations # 2,8 /sec
301 442 118 cache-misses # 28,90% of all cache refs
1,043e12 cycles # 3,21 GHz
4,118e11 instructions # 0,39 insn per cycle
- insn per cycle (IPC) - сколько инструкций ядро успевает за такт. 0,39 - это очень мало, ядро простаивает в ожидании памяти. Здоровый счётный код держит 1.0-2.0+ (а векторный и выше). Низкий IPC при высоком Busy% - классика memory-bound нагрузки, и часто это именно дальняя NUMA-память или промахи кэша.
- cache-misses ... % of all cache refs - доля промахов кэша. 28% - высоковато; чем больше промахов, тем чаще лезем в медленную RAM. Само по себе это не диагноз, но в связке с низким IPC - сильный сигнал.
- cpu-migrations - сколько раз процессы перепрыгнули между ядрами. Каждый прыжок обнуляет тёплый кэш ядра, а на NUMA ещё и может увести процесс от его памяти. Много миграций - повод привязать процесс (taskset/numactl/cgroup cpuset).
- cycles / GHz - тут же видишь реальную частоту. 3,21 GHz - turbo работает; если бы тут было 2,0 - вернулись бы к turbostat искать недоразгон.
eBPF в 2026: когда он лучше perf и strace
Перфоманс-стек повзрослел, и многое теперь делается через eBPF (ядро 5.x+, на 2026 это норма). Где он реально заменил старое:
- Профилирование вместо ручного perf record: bcc-инструмент profile снимает стек-сэмплы дёшево и сразу строит флеймграф; для разовых вопросов хватает bpftrace-однострочника.
- llcstat (bcc) - сэмплит обращения к кэшу последнего уровня и показывает hit/miss по процессам. Это тот же сигнал, что cache-misses в perf stat, но с разбивкой по PID без отдельного парсинга.
- perf c2c (cache-to-cache) - отдельно стоит знать: ловит false sharing и межсокетный пинг-понг строк кэша, прямой инструмент против NUMA-деградации.
- strace уходит на второй план: для трассировки syscall в продакшене strace тормозит процесс в разы (ptrace), а bpftrace и bcc (trace, syscount, funccount) делают то же почти без накладных расходов и без остановки процесса. strace оставляем для интерактивной отладки одного процесса, eBPF - для боевого сервера.
Типичные грабли и заблуждения
- "CPU 40% в top, значит запас есть". Нет. top не видит ни просевшей частоты, ни промахов NUMA. 40% медленных тактов хуже 40% быстрых.
- "Стоит powersave, всё пропало". На intel_pstate active powersave - это не минимальная частота, а подстройка по нагрузке. Не верь имени, смотри Bzy_MHz в turbostat.
- Governor performance не помог. Проверь питание (PkgWatt у потолка, RAPL) и температуру (CoreTmp, CoreThr). Если троттлит по теплу или по power limit - governor бессилен, чисти охлаждение или поднимай лимит.
- Привязал numactl --membind на ноду, где мало free. Получишь OOM или принудительный miss. Сначала numactl --hardware, смотри free по нодам.
- Винишь numactl, а это автобалансировщик. kernel.numa_balancing может тасовать страницы и греть other_node. Для БД с ручной привязкой его часто отключают.
- turbostat пишет "no /dev/cpu/0/msr". Нужен модуль: sudo modprobe msr. В виртуалках turbostat часто не видит частоты вообще - MSR не проброшены, это нормально (та же причина, почему частоты в облачной VM смотреть бессмысленно).
- Путаешь Avg_MHz и Bzy_MHz. Avg_MHz усреднён по всему интервалу (включая сон), Bzy_MHz - только по времени работы. Для оценки разгона смотри Bzy_MHz.
- Запусти sudo turbostat --interval 5, нагрузи систему (например stress-ng --cpu 0 или yes > /dev/null в несколько потоков по числу ядер) и смотри, как растут Busy% и Bzy_MHz, и держится ли CoreTmp ниже опасного.
- Посмотри драйвер и governor: cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_driver и scaling_governor. Если acpi-cpufreq и есть права на тестовой машине - переключи на performance через cpupower и сравни Bzy_MHz.
- Выполни numactl --hardware. Если нод две и больше - запусти numastat и оцени numa_miss и other_node относительно numa_hit. Глянь kernel.numa_balancing через sysctl.
- Сними sudo perf stat -a sleep 10 в покое и под нагрузкой, сравни insn per cycle. Если есть perf свежий - попробуй perf stat -M TopdownL1 и посмотри, Backend ли Bound.
- Какая колонка turbostat показывает реальную частоту ядра в момент работы и с чем её сравнивать, чтобы поймать недоразгон?
- Чем numa_miss отличается от other_node, и почему рост numa_miss - это плохо?
- Что означает низкий IPC (insn per cycle) при высоком Busy%, и какой инструмент даст разложение по Frontend/Backend Bound?
- Governor стоит performance, но частота всё равно низкая - какие метрики turbostat проверить и почему performance тут может быть бессилен?
top показывает загрузку, но не показывает эффективность. turbostat ловит просадку частоты, C-states и тепловой троттлинг (смотри Bzy_MHz, CoreTmp, PkgWatt, CoreThr), но помни про intel_pstate/amd-pstate - имя governor обманчиво. numactl --hardware и numastat вскрывают проблему дальней памяти на многосокетных серверах (следи за numa_miss и other_node), а kernel.numa_balancing может всё подпортить. perf stat сводит всё к одной честной цифре - IPC, а perf stat --topdown сразу скажет, упёрлись ли в память. В 2026 рядом стоят eBPF-инструменты (profile, llcstat, perf c2c), которые делают то же дешевле и адреснее strace и ручного perf record. Governor/драйвер в энергосбережении и перегрев - две самых частых причины, почему cpu частота в Linux не идёт в turbo. На обычном одно-сокетном сервере про NUMA можешь почти не вспоминать; на базах данных и тяжёлых машинах с двумя сокетами - это первое, что стоит проверить.