Что такое context switch и сколько он стоит вашему серверу
Сервер вроде бы не загружен — CPU показывает 40%, а сайт всё равно подтормаживает и запросы выполняются дольше, чем должны. Часто причина не в недостатке вычислительной мощности, а в том, что ядро тратит время не на полезную работу, а на переключение между процессами и потоками. Разберёмся, что такое context switch, почему он не бесплатен даже на самом быстром CPU и как понять, что именно он — ваша проблема.
Содержание
Что происходит при переключении контекста
Context switch (переключение контекста) — это момент, когда CPU перестаёт выполнять один поток или процесс и начинает выполнять другой. С точки зрения пользователя это происходит незаметно и постоянно: на одном ядре физически может выполняться только один поток в любой момент времени, а процессов и потоков в системе обычно намного больше, чем ядер. Планировщик ядра (scheduler) создаёт иллюзию параллельности, быстро переключаясь между ними.
Само переключение — это не бесплатная операция, а последовательность конкретных шагов, которые ядру приходится выполнять каждый раз:
- Сохранение состояния регистров. У выполняющегося потока есть набор регистров CPU — program counter, stack pointer, регистры общего назначения, флаги. Всё это нужно сохранить в структуре данных потока (в Linux это часть
task_struct), иначе поток нельзя будет корректно возобновить. - Восстановление состояния следующего потока. Симметричная операция — регистры нового потока загружаются обратно в CPU из сохранённого состояния.
- Переключение состояния MMU и контекста памяти. Между потоками одного процесса адресное пространство общее, и этот шаг дешевле. Но при переключении между разными процессами меняется таблица страниц (page table) — MMU (Memory Management Unit) начинает работать с другим отображением виртуальных адресов в физические.
- Сброс TLB-кеша (или его частичная инвалидация). TLB (Translation Lookaside Buffer) хранит недавно использованные пары «виртуальный адрес → физический адрес», чтобы не лазить в таблицу страниц на каждое обращение. При смене адресного пространства старые записи становятся невалидными. Ядро либо сбрасывает TLB целиком, либо (на CPU с поддержкой PCID/ASID) сбрасывает выборочно — дешевле, но всё равно не бесплатно.
- Планирование. Scheduler решает, какой поток запускать следующим — проходит по очередям готовности, учитывает приоритеты и время ожидания. Это тоже расходует CPU-время.
Отдельно стоит syscall — переключение между user space и kernel space (например, read() или write()). Формально это смена режима работы CPU, а не процесса, но механизм похож: сохранить контекст, переключиться, вернуться обратно. Поэтому частые syscall тоже нагружают CPU сильнее, чем кажется по загрузке в процентах.
Почему это не бесплатно даже на быстром CPU
Ключевая проблема в том, что после восстановления регистров CPU ещё не работает на полной скорости. Современные процессоры активно используют кеши L1/L2/L3 — при исполнении кода они постепенно «прогреваются»: в кеш попадают нужные данные и инструкции текущего потока. При переключении на другой поток его данных в кеше, скорее всего, нет — предыдущий поток их вытеснил. Начинается период холодного кеша (cache cold start), когда CPU чаще промахивается и вынужден лезть в оперативную память, которая на порядки медленнее кеша. То же самое происходит с TLB: после сброса первые обращения к памяти нового потока идут с промахами, и MMU каждый раз заново проходит по таблице страниц.
Поэтому реальная стоимость одного context switch — это не только сохранение/восстановление регистров (относительно быстрая операция), а ещё и косвенные потери из-за холодного кеша после переключения. Обычно вторая часть больше первой, и именно она делает частые переключения ощутимо дорогими даже на очень быстром CPU: чем быстрее ядро, тем больше полезной работы оно теряет за то же время простоя из-за промахов кеша.
Точные цифры (сколько микросекунд или циклов CPU уходит на конкретное переключение) сильно зависят от архитектуры процессора, наличия PCID/ASID, размера рабочего набора потока и ядра ОС — псевдоточные бенчмарки здесь приводить не буду, на разных серверах цифры будут заметно отличаться. Общий принцип важнее числа: переключение стоит тем дороже, чем больше данных нужно «прогреть» заново в кеше после него.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать VPSОткуда берутся частые context switch
Есть несколько типичных источников, из-за которых число переключений контекста в системе резко растёт.
Слишком много потоков или процессов относительно числа ядер. Если на сервере с 4 ядрами крутится приложение, которое создаёт 200 рабочих потоков (неудачно сконфигурированный пул воркеров), планировщику приходится постоянно ротировать их между этими 4 ядрами: каждый поток получает свой квант времени, потом переключение — и по кругу. Классический случай — веб-сервер или приложение, где размер thread pool выставлен «с запасом» без учёта реального числа CPU.
Частые системные вызовы (syscall). Программы, которые постоянно дёргают ядро — читают/пишут маленькими порциями вместо буферизации, злоупотребляют блокирующим I/O — провоцируют частые переходы user space ↔ kernel space. Каждый такой переход недёшев сам по себе, а если поток при этом блокируется в ожидании ответа от ядра, происходит ещё и полноценное переключение на другой поток.
Interrupt storm от сетевой карты. Сетевая карта генерирует аппаратное прерывание (IRQ) на каждый входящий пакет (или пачку пакетов при interrupt coalescing). Обработчик прерывания выполняется с высоким приоритетом и вытесняет текущий поток. При высокой сетевой нагрузке — много мелких пакетов в секунду, флуд, атака или просто активный трафик на VPN-сервере — число прерываний резко растёт, и CPU тратит заметную долю времени на обработку IRQ вместо полезной работы приложений. На серверах, где через один процессор проходит много сетевого трафика (VPN-шлюзы, балансировщики), это одна из частых причин странно высокой загрузки CPU при скромном трафике — похожая тема разобрана в статье про высокую нагрузку на процессор.
Избыточная многопоточность в языках и фреймворках. Некоторые рантаймы (Java с дефолтными настройками thread pool, неправильно сконфигурированный Node.js с кластеризацией) создают больше потоков/воркеров, чем физических ядер, «на всякий случай» — это усугубляет первую проблему из списка.
Виртуализация и oversubscription на хосте. На VPS отдельная тонкость: если гипервизор выделяет больше виртуальных vCPU суммарно по всем гостям, чем физических ядер на хосте, переключение контекста происходит на двух уровнях — внутри гостевой ОС и на уровне гипервизора между виртуальными машинами. Поэтому на выделенном сервере при прочих равных переключений меньше и они предсказуемее, чем на переподписанном VPS.
Как посмотреть число переключений: vmstat
Самый быстрый способ увидеть общую картину по системе — vmstat:
vmstat 1
Вывод обновляется каждую секунду, интересующая нас колонка — cs (context switches) в секцию system:
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
2 0 0 512340 81200 923410 0 0 2 8 120 850 12 3 84 1 0
Здесь in — число прерываний в секунду, cs — число переключений контекста в секунду. Смотреть нужно не на абсолютное число (оно естественно растёт с числом ядер и активности системы), а на динамику: если cs резко подскочил одновременно с ростом sy (system time) и падением us (user time), это сигнал, что система тратит непропорционально много ресурсов на переключения и обслуживание ядра, а не на прикладной код.
Точного «нормального» порога cs для всех случаев не существует — он зависит от приложения, профиля нагрузки и числа ядер. Поэтому ориентируйтесь на изменение показателя во времени относительно вашей же обычной нагрузки, а не на абсолютную цифру из чужой статьи.
Как посмотреть, кто именно переключается: pidstat -w
vmstat показывает общую картину по системе, но не говорит, какой именно процесс генерирует переключения. Для этого нужен pidstat из пакета sysstat:
# Debian/Ubuntu
sudo apt install sysstat
# AlmaLinux/RHEL
sudo dnf install sysstat
Дальше смотрим переключения контекста по процессам:
pidstat -w 1
Вывод выглядит примерно так:
Linux 6.1.0 (server) 09/01/2026 _x86_64_ (4 CPU)
12:00:01 UID PID cswch/s nvcswch/s Command
12:00:02 0 842 45.00 12.00 nginx
12:00:02 0 1210 310.00 180.00 node
12:00:02 1000 3301 2.00 0.00 sshd
Две ключевые колонки:
cswch/s— добровольные переключения (voluntary context switches) в секунду. Поток сам отдаёт CPU, потому что ждёт чего-то — обычно ввода-вывода, блокировки мьютекса, ответа от сети или диска. Высокое значение здесь часто говорит о том, что процесс много и часто ждёт I/O, а не о проблеме с CPU как таковым.nvcswch/s— принудительные переключения (non-voluntary context switches). Поток был готов работать дальше, но планировщик вытеснил его — например, у него закончился квант времени или подоспел поток с более высоким приоритетом. Высокое значение здесь — куда более тревожный сигнал: он означает, что процессу не хватает CPU-времени и он конкурирует за ядра с другими потоками/процессами.
Если нужно посмотреть по конкретному процессу или найти самый «шумный» PID, удобно сузить вывод:
pidstat -w -p ALL 1 5
и отсортировать по колонке nvcswch/s — процессы с наибольшим числом принудительных переключений и есть основные кандидаты на разбор: возможно, у них слишком много потоков, неправильно настроен affinity, или они конкурируют с чем-то ещё на этом же ядре. Заодно стоит проверить, не крутится ли на сервере что-то лишнее — этому посвящена статья про то, как узнать, кто нагружает сервер.
Когда это реальная проблема производительности
Не любое количество context switch — проблема: переключения происходят постоянно, это нормальная часть работы планировщика. Стоит начинать разбираться, когда выполняется несколько условий одновременно:
- Растёт
sy(system time), а неus(user time). CPU занят, но именно ядром, а не вашим приложением. cs/inрастут непропорционально нагрузке. Например, число запросов выросло на 20%, а число переключений — в 3 раза. Это обычно указывает на архитектурную проблему (слишком много потоков на запрос, неэффективный I/O), а не на естественный рост.nvcswch/sвысок у конкретного процесса, при этом ему явно не хватает CPU — задержки растут, хотя суммарная загрузка CPU поtopне выглядит критичной.- Задержки растут при стабильной загрузке CPU в процентах. Признак, что CPU тратит время на переключения и прерывания, а не только на вычисления — «полезная» загрузка ниже, чем показывает общий процент.
Если ни одного из этих признаков нет, а cs просто «выглядит большим» в абсолютных цифрах — поводов для беспокойства обычно нет. У активного веб-сервера с десятками воркеров счётчик переключений в принципе будет высоким, и это нормально.
Если причина — избыточное число потоков относительно ядер, вариантов решения два: уменьшить thread pool до значения, близкого к числу ядер, либо увеличить число ядер под нагрузку — на VPS апгрейд тарифа снимает конкуренцию за CPU без изменения кода. Для interrupt storm может помочь распределение обработки прерываний по нескольким ядрам (RSS/RPS) — отдельная тема за рамками этой статьи.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать VPSНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Context switch — это всегда плохо?
Нет, это нормальная часть работы многозадачной ОС. Проблема не в факте переключений, а в их избыточном количестве относительно полезной работы.
Почему на многоядерном сервере переключений меньше не становится?
Потому что число потоков обычно растёт вместе с числом ядер — рантаймы часто настраивают пул воркеров по формуле «число ядер × N». Если N выбрано с запасом, переключений может быть даже больше, чем на менее мощном, но аккуратно настроенном сервере.
Разница между cswch/s и nvcswch/s — что важнее?
Для диагностики нехватки CPU важнее nvcswch/s: она напрямую говорит, что процессу не хватает времени CPU. Высокий cswch/s чаще указывает на ожидание I/O — тоже стоит внимания, но природа проблемы другая.
Можно ли полностью избавиться от context switch?
Нет и не нужно — это фундаментальный механизм многозадачности. Речь об уменьшении избыточных переключений: разумный thread pool, эффективный I/O, достаточное число ядер под реальную нагрузку.
Помогает ли быстрый диск уменьшить число context switch?
Косвенно да, если узкое место было в ожидании диска (I/O wait). Но если причина в другом (слишком много потоков, interrupt storm), апгрейд диска не поможет — стоит сначала проверить, не упирается ли сервер именно в диск.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →