По каким признакам в профиле отличить нехватку CPU от ожидания блокировок?
При нехватке CPU потоки преимущественно выполняются на процессорах: растут загрузка CPU, время on-CPU и очередь runnable-потоков. При ожидании блокировок потоки проводят значительную часть времени off-CPU в состоянии ожидания, а профиль показывает точки блокировки и конкуренцию за общий ресурс.
Одной загрузки CPU недостаточно: при блокировках она может быть низкой, высокой из-за активного ожидания или неравномерной по ядрам. Нужно сопоставить CPU-профиль, wall-clock-профиль, состояния потоков и данные о конкуренции.
Профилирование развивалось из-за ограничения агрегированных метрик: средняя загрузка процессора и среднее время ответа показывают симптом, но не объясняют, где тратится время. Для этого разделяют on-CPU-профилирование, фиксирующее выполняющийся код, и off-CPU-анализ, показывающий ожидание блокировок, ввода-вывода, планировщика или других событий.
Такой подход особенно важен для многопоточных сервисов. Увеличение числа потоков может скрыть нехватку вычислительной мощности, но одновременно усилить конкуренцию за общий ресурс.
Если принять ожидание блокировки за нехватку CPU, команда может увеличить число экземпляров или процессоров, хотя узкое место находится в общей критической секции. Это повысит стоимость, а задержка останется прежней или ухудшится из-за роста конкуренции.
Обратная ошибка также опасна: при реальной нехватке CPU уменьшение блокировок не устранит очередь на выполнение. Поэтому нужно установить, где проходит основная часть времени запроса: в выполнении инструкций или в ожидании возможности продолжить работу.
При нехватке CPU обычно наблюдаются следующие признаки:
При ожидании блокировок характерны другие признаки:
Нужно различать ожидание блокировки и активное ожидание. При пассивном ожидании CPU может простаивать, а при spinlock или неудачно реализованном busy-wait процессор расходуется даже без полезной работы. Поэтому высокая загрузка CPU не доказывает, что приложение выполняет полезные вычисления.
Практический метод — сравнить несколько представлений одной нагрузки: CPU-время, wall-clock-время, состояния потоков, распределение задержек захвата блокировок и загрузку ресурсов. Если блокировка часто удерживается из-за медленной операции, оптимизировать нужно не только сам механизм синхронизации, но и работу внутри критической секции.
Ограничение профилирования заключается в том, что выборка может пропустить редкие или короткие блокировки, а инструментирование — изменить тайминги. Результаты следует сопоставлять с нагрузочными тестами и метриками приложения, не делая вывод по одному графику.
Сервис начал медленно обрабатывать запросы после увеличения числа рабочих потоков. CPU был загружен примерно наполовину, поэтому рассматривались три варианта: добавить процессоры, увеличить число экземпляров или исследовать конкуренцию.
Добавление процессоров имело смысл только при подтверждённой вычислительной нагрузке, но не устраняло общий ресурс. Увеличение числа экземпляров могло снизить локальную конкуренцию, однако повышало стоимость и не помогало бы, если бы узким местом была внешняя зависимость.
Wall-clock-профиль показал, что большинство потоков ожидает одну блокировку вокруг общей структуры кэширования, а CPU-профиль не содержал сопоставимой по времени вычислительной функции. Команда сократила критическую секцию и разделила структуру на независимые сегменты; после этого ожидание уменьшилось, а масштабирование потоками стало приносить пользу.
Потоки могут простаивать в ожидании блокировок, ввода-вывода, сетевых ответов или свободного соединения. В этом случае процессоры свободны, но запросы не продвигаются. Нужно анализировать время ожидания и состояние потоков, а не только процент занятости CPU.
Да. При активном ожидании поток повторно проверяет состояние ресурса и продолжает занимать процессор, не выполняя полезную работу. Высокая загрузка тогда вызвана конкуренцией, а отличить её от вычислительной нагрузки помогают стек активного ожидания, рост числа неэффективных попыток захвата и отсутствие пропорционального прироста полезной обработки.
После устранения первого ограничения проявляется следующий: например, другая блокировка, очередь к пулу соединений или ограничение внешней зависимости. Кроме того, блокировка могла быть лишь местом, где накапливалось ожидание, а исходная причина находилась в слишком долгой операции внутри критической секции. Поэтому после изменения нужно повторить измерение и проверить, переместилось ли узкое место, а не считать успехом только снижение одной метрики.