Процессор, который экономит энергию, и процессор, который отвечает мгновенно, - это два разных режима мышления одного кристалла. Глубокие состояния простоя позволяют серверу не греть воздух, но пробуждение ядра из глубокого сна стоит десятки, а то и сотни микросекунд, которые в торговых системах, телекоме и обработке потоковых данных лежат прямиком в бюджете задержки на операцию. Разберём устройство состояний C, как их настраивают под задачи с жёсткой латентностью и где проходит граница между разумной экономией и самострелом в производительность.

Что такое C-states и сколько стоит пробуждение

Буквой C обозначают состояния простоя ядра процессора. В C0 ядро исполняет код. На каждую следующую ступень - C1, C1E, C3, C6 и глубже - ядро отключает всё больше своих внутренностей: сначала останавливается тактирование, затем сбрасываются напряжения, потом выключаются кэши с потерей их содержимого. Глубже сон - меньше потребление в покое, но тем длиннее обратная дорога: содержимое кэша нужно набирать заново, напряжения восстанавливать, и цена входа вырастает на два порядка между крайними ступенями. Опубликованные измерения дают такие ориентиры: для C1 время выхода составляет 1-3 микросекунды, для C6 от 50 до 100 микросекунд, а в работах по архитектурам для нагрузок с жёсткой латентностью приводят значение порядка 133 микросекунд на вход и выход из C6. Сводно это выглядит так:

Состояние   Что отключено                  Время выхода    Относительная цена
C0          ничего, ядро исполняет код     0               1x
C1          остановлено тактирование       1-3 мкс         1x
C1E         снижено напряжение ядра        единицы мкс     1-3x
C6          сброшено напряжение, кэш потерян  50-133 мкс    20-60x
Пакетный C  спит весь сокет                сотни мкс и выше

Отсюда следует практический вывод, который часто упускают: разница между C1 и C6 это не проценты, а десятки раз, и именно поэтому профиль электропитания так сильно двигает девяносто девятый перцентиль.

Главная хитрость заключается в том, что латентность платит не простой, а работа. Пока ядро спало, очередь задач копится; когда ядро просыпается, первый запрос ждал не только своё обслуживание, но и само пробуждение. На измеренных графиках это видно как хвост распределения: медианная задержка красивая, а девяносто девятый перцентиль утроен.

Считать этот хвост удобно через вероятность того, из какого состояния ядро будет разбужено. Среднюю плату за пробуждение даёт взвешенная сумма, а перцентиль определяется самым глубоким состоянием, которое вообще разрешено:

T_wake_avg = p_deep * T_exit_deep + p_shallow * T_exit_shallow
T_p99      ~ T_exit_deep, если глубокий сон разрешён

p_deep = 0.3, p_shallow = 0.7, T_exit_deep = 133 мкс, T_exit_shallow = 2 мкс:
T_wake_avg = 0.3 * 133 + 0.7 * 2 = 41.3 мкс
T_p99      = 133 мкс и выше

Запрет глубокого сна:
T_wake_avg = 2 мкс, T_p99 = 2-3 мкс

Иными словами, ограничение глубины сна убирает не среднее, а именно хвост, и средняя задержка при этом почти не меняется. Поэтому в отчётах о тюнинге медиана часто остаётся прежней, а перцентили падают в десятки раз, и смотреть нужно исключительно на распределение. Для равномерно нагруженных систем, где ядра не успевают засыпать, тема почти не беспокоит. А вот характерный профиль "короткие пакеты раз в несколько сотен микросекунд" - ровно тот климат, в котором глубокие C-states показывают список своих зубов.

Полезно знать про взаимодействие с соседними механизмами. Чем глубже C-state, тем больше шок по периферии: пробуждение расталкивает подсистему когерентности, и на многосокетных машинах глубокий сон отдельных ядер влияет и на время межсокетных обращений. Поэтому настройка редко бывает изолированной - её проверяют в связке с профилями P-state (частот) и с параметрами прерываний, о которых серверные инженеры говорили отдельно.

Кто управляет сном и как превратить это в настройку

Формально решение о входе в C-state принимает операционная система. По устройству: планировщик эталона Windows считает прогноз простоя каждого ядра и выбирает состояние, которое наверняка окупится. Значение "наверняка" зависит от настроек схемы электропитания. В знакомом всем диалоге схем питания живут параметры с длинными именами, а у системных администраторов есть полный программный доступ к ним через powercfg. Показать скрытые регуляторы и выставить их из командной строки можно без перезагрузки:

rem  сначала смотрим, какие состояния вообще доступны на этом железе
powercfg /a

rem  снимаем атрибут скрытости с нужных регуляторов подгруппы процессора
powercfg -attributes SUB_PROCESSOR IDLEDISABLE -ATTRIB_HIDE
powercfg -attributes SUB_PROCESSOR CPMINCORES -ATTRIB_HIDE

rem  запрещаем глубокий сон и держим минимальное число непаркуемых ядер на максимуме
powercfg -setacvalueindex SCHEME_CURRENT SUB_PROCESSOR IDLEDISABLE 1
powercfg -setacvalueindex SCHEME_CURRENT SUB_PROCESSOR IDLEPROMOTE 0
powercfg -setacvalueindex SCHEME_CURRENT SUB_PROCESSOR IDLEDEMOTE 0
powercfg -setacvalueindex SCHEME_CURRENT SUB_PROCESSOR CPMINCORES 100

rem  минимальная частота бодрствующего ядра на 100 процентов от номинала
powercfg -setacvalueindex SCHEME_CURRENT SUB_PROCESSOR PROCTHROTTLEMIN 100
powercfg -setactive SCHEME_CURRENT

rem  контроль: что реально записалось в схему
powercfg /q SCHEME_CURRENT SUB_PROCESSOR

Команда powercfg /a печатает список состояний, доступных системе, и именно с неё начинают диагностику: если в выводе нет строки про C6, значит потолок уже выставила прошивка и крутить схему питания бессмысленно. Разблокировка глубокого сна управляется параметром IDLEDISABLE; границы повышения и понижения состояния задаются парами процентов загрузки, выше которых система выбирает более мелкий сон; CPMINCORES отвечает за минимальное число ядер, которые парковщик не имеет права отправить спать. Отдельный рычаг - HETEROPOLICY для гетерогенных ядер у новых платформ, но в строго серверном мире пока чаще балом правит классический набор.

Если нужен готовый агрессивный профиль без ручной настройки каждого регулятора, его копируют из встроенного:

powercfg -duplicatescheme e9a42b02-d5df-448d-aa00-03f14749eb61
powercfg -list

Эта команда создаёт в системе дубликат схемы максимальной производительности и печатает её новый GUID, который дальше можно раздавать по парку серверов через групповые политики.

На уровне прошивки настройки зеркалятся ещё раз: в UEFI сервера живут пункты управления C-states, лимитом глубины (package C-state limit) и отдельные переключатели вроде мониторинга MWAIT. Правило, которое за годы тюнинга выкристаллизовалось у практиков: уровень прошивки ставит потолок, уровень ОС работает внутри потолка. Выключил C6 в прошивке - никакая схема питания не усыпит ядро глубже; разрешил там C6, но запретил глубокий сон в Windows - ядра будут дремать на мелководье. Диагностическая ловушка известная: настройки двух уровней молча расходятся, и инженер ищет причину в драйверах, пока не сверит обе стороны.

Как убедиться измерением, что хвост задержек даёт именно пробуждение ядра из сна

Прежде чем подтягивать настройки, стоит доказать себе, что хвост задержек вызван пробуждением, а не конкуренцией за ядра или переполнением кэша. Классическая методика состоит из трёх наблюдений. Первое - распределение времени отклика на длинной петле теста: у проблем со сном характерный профиль "холм плюс редкий высокий хвост". Второе - счётчики, показывающие процент времени, проведённого ядрами в каждом состоянии; их видно и системным монитором, и библиотекой PMC, и встроенной телеметрией гипервизора для виртуальных машин. Третье - грубый, но честный эксперимент: запретите глубокие состояния и повторите тест. Хвост схлопнулся, энергопотребление выросло на десятки ватт на сокет - диагноз подтверждён, и дальше речь только о цене вопроса.

Набор инструментов стандартный. Отчёт об энергоэффективности собирают одной командой и читают в нём раздел про простои ядер:

powercfg /energy /duration 60 /output C:\PerfLogs\energy-report.html
powercfg /systemsleepdiagnostics
wpr -start Power -start CPU -filemode
rem  прогон целевой нагрузки
wpr -stop power-trace.etl

Запись с профилем Power ловит переходы между состояниями, и в анализаторе видно и долю времени в каждом состоянии, и частоту пробуждений. Внешний анализатор вроде Intel PCM показывает процент резиденции ядер в C-состояниях по сокетам, а для виртуализированных стоек остаются счётчики хоста, потому что гость про своё ядро знает лишь то, что гипервизор согласился ему рассказать. Отдельную главу образуют настройки таймера: режим высокого разрешения с периодом 0,5-1 мс сам по себе не даёт системе засыпать глубоко, и включение его "для экономии латентности" - завуалированный способ запретить глубокий сон всей машиной; в роли временного костыля приемлемо, в роли решения - нет.

Стратегии настройки под разные профили нагрузки

Теория сходится в простую матрицу решений, которую серверные команды за годы выверили:

  1. Жёсткая экономия с длинными паузами (бэкапы, ночные задачи): минимальные ограничения, глубокие состояния разрешены, задержка на старте пакета прощена;
  2. Равномерный поток (веб-фронтенды, очереди): умеренный компромисс, глубокий сон разрешён, но порог повышения поднят выше, чтобы короткая пробка не уронила отклик;
  3. Системы с бюджетом на перцентиль (трейдинг, потоковая обработка, телефония): глубокие C-states ограничены, отдельные гнезда ядер полностью удерживаются в C1, приём трафика привязан к "бодрствующим" ядрам;
  4. Виртуальные стойки: настройки ведутся на хосте, гостям топология сна не сообщается, при этом выбор лимита глубины делается для всей машины целиком с учётом самого чувствительного жильца.

В остром варианте из третьей строки встречается и ручное распределение ролей: одна группа ядер обслуживает сетевой тракт и никогда не спит глубже поверхностной дремы, вторая обрабатывает данные и может спать свободно. На современных серверах с полусотней ядер лишиться сна у восьми ядер из сорока восьми - небольшая жертва ради ровного хвоста распределения.

Связь с P-state, турбо и подсистемой прерываний

Настройка состояний сна не живёт отдельно от настройки частот, и путать эти слои - любимая ошибка новичков. P-states решают, с какой частотой работает бодрствующее ядро, C-states - как глубоко оно спит между работой. Проблема латентности обычно составная: ядро не только проснулось долго, но ещё и выходит из сна на пониженной частоте, потому что политика частот консервативна. Поэтому профиль латентно-чувствительного сервера традиционно включает три пункта: ограничение глубины C-states, поднятие минимальной частоты в схеме питания (значение в процентах через тот же powercfg, параметр PROCTHROTTLEMIN) и разрешение турбо-режима, чтобы пиковая нагрузка гасилась частотой, а не очередью.

Вторая связка - с обработкой прерываний. Каждое входящее прерывание - будильник: оно поднимает ядро из сна, и если задержка обработки сетевого пакета важна, распределение прерываний договаривается с политикой сна. Отсюда вырастает уже упомянутый приём с разделением ролей: ядра, которым назначены очереди сетевого адаптера, удерживаются мелко спящими, а остальным разрешено всё. На серверах с модерацией прерываний баланс хитрее: слишком агрессивная модерация сама по себе добавляет задержку, и в связке с глубоким сном эффекты складываются: пакет приехал, подождал соседей по пачке, разбудил ядро, ядро ещё и очнулось не сразу. Латентно-ориентированная конфигурация в этой паре всегда жертвует экономией прерываний в пользу немедленной доставки.

Третья, тонкая связь - с планировочными механизмами парковки ядер. Система старается консолидировать работу на части ядер, чтобы остальные уходили в глубокий сон; для задач, которым критичен разброс задержки, это поведение вредно, так как периодическая переброска потока на вновь проснувшееся ядро добавляет скачки. Отключение парковки (известный параметр управления распределением ядер в той же семье настроек процессора) - стандартный пункт профиля "высокая производительность" на латентно-чувствительных машинах.

Эталонные величины, на которые ориентируются в практике

Конкретные цифры у разных платформ разные, но порядки величин стабильны, и без них разговор ощущается голым. Выход из C1 занимает единицы микросекунд и для большинства задач незаметен. Переходы уровня C3 добавляют десятки микросекунд. Глубокие состояния C6 и пакетный сон сокета оцениваются уже сотнями микросекунд, а в плохих конфигурациях прошивки - и единицами миллисекунд. Разница между "все ядра в C1" и "свободный глубокий сон" по потреблению в покое для двухсокетной машины легко переваливает за сотню ватт. Именно эти порядки стоят за простым рабочим правилом: если бюджет задержки на операцию измеряется единицами миллисекунд, глубокий сон почти не мешает, и заниматься настройкой стоит только ради экономии; если бюджет лежит в десятках микросекунд, глубокий сон запрещают в первую очередь. Проверить правило на своих числах легко: плата за пробуждение из C6 в 133 микросекунды составляет 13 процентов от бюджета в одну миллисекунду и в шесть раз превышает бюджет в 20 микросекунд.

Приведённый сквозной пример из опыта одной телекоммуникационной площадки иллюстрирует масштаб: продукт, обрабатывавший биржевой поток, показал девяносто девятый перцентиль отклика в 220 микросекунд на стандартной схеме питания против 45 микросекунд на профиле без глубокого сна, при росте потребления всей стойки на восемь процентов. Такие пропорции - обычная плата за предсказуемость, и взвешенное решение здесь принимается не интуицией, а сравнением стоимости ватт со стоимостью хвоста распределения в деньгах заказчика.

Для полноты картины проверку настройки удобно автоматизировать: небольшой скрипт после каждой перезагрузки сверяет действующие значения схемы питания с эталоном и рапортует о любом расхождении, потому что обновления системы и драйверные пакеты иногда молча возвращают схему к умолчанию. Минимальная рабочая версия такого контроля занимает полтора десятка строк:

$baseline = @{
    IDLEDISABLE     = 1
    PROCTHROTTLEMIN = 100
    CPMINCORES      = 100
}

$out = powercfg /q SCHEME_CURRENT SUB_PROCESSOR | Out-String
foreach ($k in $baseline.Keys) {
    if ($out -notmatch "$k[^\n]*?Current AC Power Setting Index:\s+0x([0-9a-fA-F]+)") {
        Write-Warning "регулятор $k не найден в выводе, требуется проверка вручную"
        continue
    }
    $actual = [Convert]::ToInt32($Matches[1], 16)
    if ($actual -ne $baseline[$k]) {
        Write-Warning "дрейф конфигурации: $k = $actual, эталон = $($baseline[$k])"
    }
}

Запуск такого скрипта планировщиком на старте системы превращает молчаливый откат настроек в событие журнала, которое кто-нибудь обязательно увидит.

Энергетический баланс и последствия для эксплуатации

Отключение глубоких состояний - покупка задержки за ватты, и цену честно считают на калькуляторе. Держать 96-ядерный сервер на мелком сне против свободного - значение порядка 60-150 ватт на машину в покое, на стойку сумма уже заметная, а в годах - отдельная статья бюджета. Поэтому зрелая практика - не глухой запрет, а точечное разрешение: для большинства узлов кластера спокойная схема питания, для латентно-чувствительных - жёсткая, а различие между ними фиксируется в описании роли сервера, а не в памяти администратора.

Итоговая дисциплина простая: доказать проблему хвостом распределения, подобрать конфигурацию на стенде с реальным профилем нагрузки, описать обе стороны настройки - прошивку и ОС, вложить результат в систему управления конфигурациями и контролировать дрейф по счётчикам резиденции. Там, где эта дисциплина соблюдена, сервер даёт предсказуемые перцентили и днём, и в моменты "пустого" трафика, а сказка про процессор, который то спит, то отвечает за рекордные микросекунды, остаётся в рекламных буклетах. Такая же проверка через реестр параметров политики питания заодно ловит и несанкционированные изменения, внесённые сторонними оптимизаторами, чья репутация среди серверных инженеров давно ниже плинтуса.