По работе я постоянно имею дело с серверами; при этом их владельцы всегда хотят знать, когда серверы используют свои ресурсы максимально. Вроде бы, это простая задача? Достаточно настроить top
или другой инструмент мониторинга системы, посмотреть на процент использования сети, памяти и CPU, и наибольшее значение покажет, насколько близко сервер находится к пределу своих возможностей.

Однако когда владельцы пытаются реально проецировать эти значения, то оказывается, что процент использования CPU на самом деле растёт не совсем линейно. Но насколько непрямой может быть зависимость?
Чтобы ответить на этот вопрос, я выполнил кучу стресс-тестов, мониторя при этом объём выполняемых ими работы и отображаемый системой уровень использования CPU, а затем по результатам построил графики.
Подготовка
В качестве тестовой машины я использовал десктоп Ubuntu с процессором Ryzen 9 5900X (12 ядер / 24 потока). Также я включил Precision Boost Overdrive (то есть Turbo).
При помощи вайб-кодинга я написал скрипт, выполняющий в цикле stress-ng, сначала с использованием 24 воркеров, попробовав выполнять их каждый при разных уровнях нагрузки на CPU, от 1% до 100%, а затем использовал от 1 до 24 при нагрузке 100%. Тест использовал разные методики нагрузочного тестирования и измерял количество операций, которые можно было выполнить («Bogo ops1»).
Я проводил две методики тестирования, потому что операционные системы умно используют свой планировщик, поэтому планирование небольшого количества воркеров с использованием процессора на 100% можно реализовать оптимально, но когда все 24 воркера находятся на уровне использования 50%, операционной системе сложно сделать что-то иное, нежели распределить работу равномерно.
Результаты
Сырые результаты в CSV можно посмотреть здесь: https://docs.google.com/spreadsheets/d/1QKdYa3NIFGTixG_LdnsbwbeLDnE2GNVVsS9-dfkcT20/edit?usp=sharing.
Общий тест CPU
Самый простой тест просто выполняет в цикле все стресс-тесты CPU утилиты stress-ng.

Как мы видим, когда система сообщает об использовании CPU на 50%, на самом деле он выполняет 60-65% от реальной максимальной работы.
64-битная целочисленная математика
Хм, возможно, это была случайность? Что, если просто выполнять какие-то произвольные вычисления с 64-битными integer?

Здесь картина ещё печальнее! При «использовании на 50%» мы на самом деле выполняем 65-85% от максимальной работы. Ну, хуже ведь уже быть не может?
Матричные вычисления

Что-то тут определённо не так. При выполнении матричных вычислений «использование на 50%» — это, на самом деле, от 80% до 100% максимально возможного объёма работы.
На скриншоте с монитором системы из начала статьи показан тест матричных вычислений с 12 воркерами; можно увидеть, что он сообщал об использовании на 50%, несмотря на то, что дополнительные воркеры абсолютно ничего не делают (за исключением повышения значений использования ресурсов CPU).
Бонус: Nginx
На Hacker News мне предложили провести реальный бенчмарк, поэтому я прогнал бенчмарк Nginx из Phoronix Test Suite, ограниченный 1-24 ядрами (к сожалению, я не мог более точно контролировать процент использования CPU, поэтому получился только один график).

Здесь мы видим, что изначально определяемый процент использования ниже реального, а затем ситуация ухудшается. При использовании на 50% на самом деле выполняется 80% от максимального количества запросов в секунду, а при 80% мы на самом деле уже на 100% от максимального объёма запросов.
Что происходит?
Hyperthreading
Можно заметить, что график меняется на 50%, поэтому я добавил кусочно-линейные регрессии, показывающие выравнивание.
Главная причина происходящего — hyperthreading: половина «ядер» на этой машине (и на большинстве машин) делит ресурсы с другими ядрами. Если я запущу на этой машине 12 воркеров, то каждому из них назначат отдельное физическое ядро без общих ресурсов, но при превышении их количества каждый дополнительный воркер будет делить ресурсы с другим. В некоторых случаях (общие бенчмарки CPU) это лишь немного ухудшает ситуацию, а в других (матричные вычисления с активным применением SIMD) не остаётся ресурсов, которые можно делить.
Turbo
Это сложнее заметить, но Turbo тоже влияет на результаты. При низком уровне использования этот конкретный процессор работает на частоте 4,9 ГГц, но с увеличением количества активных ядер частота медленно снижается до 4,3 ГГц2.

Посмотрите на ось Y. На этом процессоре тактовая частота падает «всего» на 15%.
Так как процент использования ресурсов CPU рассчитывается как соотношение занятых тактов и общего их количества, с увеличением числителя знаменатель становится меньше, и это ещё одна причина, по которой реальное использование CPU возрастает быстрее, чем линейно.
Важно ли это?

Если вы смотрите на уровень использования CPU и предполагаете, что он будет возрастать линейно, то у вас возникнут проблемы. Если вы используете CPU эффективно (показатель использования выше «50%»), то отображаемый уровень использования на самом деле преуменьшен, и иногда существенно.
Имейте также в виду, что я показал результаты только для одного процессора, но производительность hyperthreading и поведение Turbo могут сильно варьироваться для разных моделей процессоров, в особенности от разных производителей (AMD и Intel).
Наилучший известный мне способ частичного решения этой проблемы — прогонять бенчмарки и мониторить выполняемую реальную работу:
Замерять бенчмарком, какой объём работы может выполнять сервер, прежде чем начнут возникать ошибки или нежелательные уровни задержек.
Отслеживать, какой объём работы выполняет сервер на текущий момент.
Сравнивать эти две метрики, а не показатель использования CPU.
Bogo ops — это, вероятно, отсылка к BogoMIPS, то есть к «фиктивному» («bogus») бенчмарку, выполняемому Linux при запуске для крайне приблизительной оценки производительности CPU.
Одно из основных ограничений работы процессоров — это необходимость достаточно быстрого рассеяния тепла. Когда работает только одно ядро, процессор может дать этому ядру немного свободы в тепловыделении, которое не тратится на другие ядра, чтобы оно работало быстрее, но это невозможно, если работают все ядра. Энергопотребление работает похожим образом и в некоторых средах тоже может быть ограничением (в десктопах обычно такого не бывает, но часто встречается на серверах).
Комментарии (8)
l1kus
05.09.2025 09:29В играх примерно такая же проблема: из-за того, что большинство из них не умеет грамотно работать с потоками, может казаться, что при загрузке процессора в 50% у тебя остаётся запас по производительности, но на самом деле программа просто не до конца использует весь ресурс процессора, который может. Таким образом, независимо от загрузки процессора в диспетчере задач, это не покажет реальную производительность процессора, поскольку это просто сумма всех используемых потоков. Поэтому, если у тебя многоядерный процессор, это ещё не значит, что все программы будут использовать все ядра.
MainEditor0
05.09.2025 09:29Именно поэтому производительность на ядро решает много проблем. В идеале иметь много мощных ядер и многопоточность, чтобы ни в одном из кейсов процессор не сел в лужу...
Okeu
05.09.2025 09:29Мне кажется вы(ну или исходный автор) немного неверно интерпретировали графики. Метрика нагрузки CPU действительно ложная метрика, но у вас она вышла таковой именно из-за того, что % нагрузки считается общим, а 0-11 и 12-23 воркеры друг другу не равны. Не все задачи могут эффективно выполняться на логических потоках процессора. А в статье идет прямо таки упор на это.
Для меня ложность %% нагрузки чего-либо заключается в том, что может внезапно оказаться, что даже при значениях близким к 100% - можно еще очень даже много полезной работы засунуть в конвеер, и наоборот (реже)
Это обобщенная сферическая в вакууме размазанная во времени характеристика, которая во многих кейсах совершенно неинформативная, а вот ее АНОМАЛЬНОЕ поведение, в совокупности с прочими параметрами - уже может что-то нам сказать. УПД: ее еще и разные системы по-разному считают. Поэтому она для меня всегда являлась косвенным параметром.
Ivan22
05.09.2025 09:29оказывается одна цифра плохо подходит для описания нагрузки современных процессоров с кучей ядер, потоков, гипертредингов и прочая, и прочая
wataru
Тут не только нелинейность, бывает вообще немонотоность. Вот соптимизировали вы вашу программу, а % загрузки ЦПУ только подрос! Это потому, что ЦПУ сбрасывает частоту по возможности и уже меньший объем работы занимает более значительную часть сильно сжавшегося пирога. Это характерно лишь для некоторых типов работы, но так бывает, например, когда у вас куча слабо загруженных очередей задач да еще и с задержками перед выполнениями.
Более полезной метрикой оказывается потребляемая мощность процессора.
MountainBug
А что, если у меня установлен верхний предел энергопотребления, но моя задача настолько ресурсоемкая, что, скажем, выделяя на ее решение 12 из 16 ядер, я уже упираюсь в лимит? Получается, что часть ресурсов то фактически еще свободна, 4 ядра я не занял вообще.
knstqq
что ж, похоже вам значит такой подход не подходит. У вас мобильная разработка?