Практическое руководство по максимальному приближению к мягкому реальному времени на обычной Windows

· · Разработка Windows, Мягкое реальное время, Проектирование, Измерения

Когда на Windows создают процессы, для которых «задержка — это проблема», — периодическую обработку, обработку аудио, обработку видео, измерения, управление оборудованием, — часто складывается впечатление, что «на Windows это тяжело». Это впечатление наполовину верно, а наполовину нет: Windows не является ОС hard real-time, но если тщательно проработать проектирование, реализацию, измерения и эксплуатацию, систему можно довести до вполне практичного состояния soft real-time.

В этой статье речь идёт об обычной Windows 10 / 11 — без специальных расширений RTOS, собственных драйверов ядра или выделенных контроллеров. Это практический разговор о том, насколько далеко можно продвинуться в снижении задержки и джиттера с помощью пользовательского (user-mode) приложения на обычном настольном или портативном ПК. У аудио, видео, периодического управления и сбора данных детали различаются, но проблемные места во многом общие, поэтому на этот раз эта общая часть собрана в виде чек-листа.

Содержание

  1. Сначала вывод (в двух словах)
  2. Что значит «мягкое реальное время» на обычной Windows
    • 2.1. Что в этой статье понимается под «обычной Windows»
    • 2.2. Что достижимо, а где начинаются трудности
    • 2.3. Сначала — коротко о терминах
  3. Основные причины задержки и джиттера
    • 3.1. Планировщик и приоритеты
    • 3.2. DPC / ISR и драйверы
    • 3.3. Page fault и память
    • 3.4. Разрешение таймера и управление питанием
    • 3.5. Миграция между ядрами и нагрев
  4. Практический чек-лист снижения задержек на обычной Windows
    • 4.1. Периодический цикл и способ ожидания
    • 4.2. fast path / slow path и очередь фиксированной длины
    • 4.3. Приоритеты / MMCSS / background mode
    • 4.4. Память / GC / затраты первого запуска
    • 4.5. Настройки питания / EcoQoS / разрешение таймера
    • 4.6. Размещение по CPU / миграция между ядрами / нагрев
    • 4.7. Разделение драйверов / DPC / ISR / внешних помех
  5. Измерение и оценка
    • 5.1. Что записывать
    • 5.2. Как читать p99 / p99.9 / max
    • 5.3. Чем измерять
    • 5.4. Дисциплина тестирования
  6. Краткое руководство по выбору
  7. Итог
  8. Источники

1. Сначала вывод (в двух словах)

  • На обычной Windows цель — не гарантия hard real-time, а конфигурация soft real-time: «маловероятно опоздать, и не сломаться, даже если опоздали»
  • Самый большой эффект даёт то, что горячий путь становится коротким, фиксированной длины и неблокирующим
  • Fast path (получение / управление) и slow path (сохранение / связь / UI) разделяют, а между ними ставят очередь фиксированной длины
  • Периодический цикл прогоняют не полагаясь на Sleep(1), а по абсолютному сроку
  • Для непрерывных потоков вроде аудио и видео в первую очередь рассматривают MMCSS
  • Для измерения времени используют QueryPerformanceCounter (QPC), а в .NET — Stopwatch
  • Для ожидания предпочитают события устройства или высокоточный waitable timer
  • timeBeginPeriod используют только на время реальной необходимости. Не проектируют исходя из того, что он включён постоянно
  • В реальной эксплуатации помогают питание от сети переменного тока / режим питания / обращение с EcoQoS / наведение порядка в фоновой нагрузке
  • Оценивают не только по среднему значению, а по p99 (граница, где начинает быть видно самое медленное 1 измерение из 100) / p99.9 / max / числу пропусков / DPC / ISR / page fault / глубине очереди

Иными словами, на обычной Windows эффективнее не повышение приоритета, а сокращение причин задержки средствами проектирования. Приоритеты и настройки питания важны, но одни они стабильности не создают.

2. Что значит «мягкое реальное время» на обычной Windows

2.1. Что в этой статье понимается под «обычной Windows»

Под обычной Windows здесь примерно подразумевается следующее.

  • обычный настольный или портативный ПК с Windows 10 / 11
  • без собственных расширений RTOS
  • без разработки собственных драйверов режима ядра
  • обычное user-mode приложение
  • настройка с помощью стандартных Windows API и параметров

То есть речь идёт не о том, чтобы «полностью собрать отдельную машину под управление реальным временем», а о том, насколько реалистично можно продвинуться на обычном Windows-ПК.

Обычный ПК с Windows 10 / 11Приложение user-modeЦель — мягкое реальное времяСнизить задержкуУменьшить джиттерНаблюдать пропуски сроков и не ломатьсяНужна гарантия нулевых нарушений сроковRTOS / выделенный контроллер / FPGA / обработка на стороне устройства

2.2. Что достижимо, а где начинаются трудности

Даже на обычной Windows, например, для такой обработки можно вполне реалистично создать состояние «редко опаздывает».

  • периодическая обработка с интервалом от нескольких миллисекунд до нескольких десятков миллисекунд
  • буферный режим аудио / видео
  • сбор данных с датчиков и циклы управления
  • периодическая обработка в духе программного ПЛК
  • конвейер с низкой задержкой, работающий в отдельном от UI потоке

При этом «можно» здесь не означает, что редкие всплески задержки удастся полностью свести к нулю — цель, скорее, состоит в следующем.

  • снизить задержку в обычном режиме
  • уменьшить джиттер
  • не ломаться, даже если срок иногда пропущен
  • уметь наблюдать сам факт пропуска

И наоборот, следующие требования выполнить одним лишь user-mode на обычной Windows довольно тяжело.

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

В таких случаях безопаснее также рассмотреть перенос только действительно критичных по времени участков на firmware устройства, выделенный контроллер, FPGA или RTOS.

2.3. Сначала — коротко о терминах

Сначала зафиксируем значение терминов, которые встречаются в этой статье.

Термин Коротко Практический взгляд
soft real-time (мягкое реальное время) Подход, при котором редкие задержки допустимы, но их стараются сделать небольшими и не ломающими систему Именно это в первую очередь стоит выбирать целью на обычной Windows
hard real-time (жёсткое реальное время) Мир, где нужно гарантировать нулевые нарушения сроков Не то, чего стоит добиваться одним лишь user-mode на обычной Windows
Джиттер Колебание периода или времени отклика Даже при хорошем среднем значении большой джиттер означает нестабильность в реальной эксплуатации
Пропуск срока (deadline miss) Обработка не завершилась к запланированному времени Не скрывать, а считать и записывать в лог
p99 / p99.9 Показатели для взгляда на медленный «хвост» p99 — это «граница, где начинает быть видно самое медленное 1 измерение из 100»
DPC / ISR Обработка на стороне ядра вокруг драйверов и прерываний Если она длинная, user-mode-поток вынужден ждать
MMCSS Механизм Windows, распределяющий CPU для чувствительной ко времени обработки (аудио / видео) Сильный вариант для обработки, которой нельзя допускать опустошения буфера
QPC QueryPerformanceCounter Основа измерения прошедшего времени. Высокоточный счётчик, а не настенные часы

3. Основные причины задержки и джиттера

Причины задержки периодической обработки на обычной Windows почти всегда сводятся к одному из пунктов этой схемы.

Периодическая обработка запаздываетПланировщик / приоритетыDPC / ISR / драйверыPage fault / памятьРазрешение таймера / управление питаниемМиграция между ядрами / нагрев

3.1. Планировщик и приоритеты

Порядок выполнения потоков Windows определяется приоритетом. При равном приоритете потоки идут по кругу (round-robin), а когда становится готовым к выполнению поток с более высоким приоритетом, поток с более низким приоритетом отодвигается в сторону.

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

  • другие потоки
  • другие процессы
  • внутренняя обработка ОС
  • продукты безопасности
  • вспомогательная обработка устройств
  • фоновая синхронизация

3.2. DPC / ISR и драйверы

Этот момент довольно важен. Даже если привести в порядок приоритеты на стороне приложения, если DPC (Deferred Procedure Call) или ISR (Interrupt Service Routine) выполняются долго, всё это время user-mode-поток выполняться не может.

Частыми причинами становятся такие устройства и драйверы:

  • USB
  • Wi-Fi / Bluetooth
  • хранилище
  • аудио
  • GPU
  • ACPI / управление питанием

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

3.3. Page fault и память

Если в горячем пути возникает page fault (нужная страница отсутствует в памяти, и её приходится подгружать), задержка резко увеличивается.

Особенно стоит избегать таких паттернов:

  • фиксация страницы при первом обращении
  • отложенная загрузка
  • подкачка страниц memory-mapped файлов
  • динамическое выделение сверх необходимого
  • большие объекты или фрагментированная куча

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

3.4. Разрешение таймера и управление питанием

Подход «хочу выполнять действие каждую 1 мс, поэтому Sleep(1)» почти никогда не срабатывает. Точность ожидания в Windows зависит от разрешения таймера, планирования и состояния питания.

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

3.5. Миграция между ядрами и нагрев

Когда поток мигрирует между ядрами, кешу приходится «прогреваться» заново. Зачастую ОС сама неплохо с этим справляется, но в условиях высокой нагрузки это становится источником колебаний.

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

4. Практический чек-лист снижения задержек на обычной Windows

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

4.1. Периодический цикл и способ ожидания

Прежде всего, вот классический антипаттерн.

while (running)
{
    Sleep(1);
    Step();
}

Это не «период 1 мс», а цикл, который обычно ждёт чуть больше 1 мс, а сверху добавляет время выполнения Step(). Причём перерасход ожидания накапливается без изменений.

На основе абсолютного срокаНа основе относительного времениWaitUntil(next - margin)next += periodКороткий spin при необходимостиFastStep()Step()Sleep(1)Ошибка ожидания и время выполнения постепенно накапливаютсяМеньше подвержен накоплению дрейфа

Чек-лист

  • Периодический цикл не построен на основе Sleep(1)
  • Период прогоняется по абсолютному сроку через next += period
  • Для ожидания отдаётся предпочтение событиям устройства или waitable timer
  • Только финальная подстройка ограничена очень коротким busy-spin (ожиданием с активным опросом)
  • timeBeginPeriod используется только на время необходимости, после чего значение возвращается
  • Поведение проверено также в свёрнутом / скрытом / невидимом состоянии

Периодический цикл стабильнее прогонять не по относительному времени, а по абсолютному сроку.

int64_t next = QpcNow() + periodTicks;

while (running)
{
    WaitUntil(next - wakeMarginTicks);

    while (QpcNow() < next)
    {
        CpuRelax(); // Короткий spin только в самом конце
    }

    int64_t started = QpcNow();
    FastStep();
    int64_t finished = QpcNow();

    RecordTiming(next, started, finished);

    next += periodTicks;

    while (finished > next)
    {
        ++missedDeadlines;
        next += periodTicks;
    }
}

4.2. fast path / slow path и очередь фиксированной длины

Основа архитектуры — размещать в fast path только «работу, чувствительную к срокам», а всё остальное выносить в slow path.

Устройство / события полученияfast path: получение, управление, минимальное копированиеОчередь фиксированной длиныslow path: сохранение, отправка, UI, агрегацияЗапись lateness / miss / глубины очереди

То, что делается в fast path, стоит ограничить примерно этим списком.

  • получение данных
  • вычисление управляющих значений
  • минимально необходимое копирование
  • проставление временной метки
  • постановка в очередь
  • запись miss / overrun

Всё остальное сбрасывается в slow path.

Чек-лист

  • В горячем пути нет записи в файл, сетевой отправки, записи в БД
  • В горячем пути нет тяжёлого логирования, Flush, синхронного RPC
  • fast path и slow path чётко разделены по потокам или зонам ответственности
  • Очередь имеет фиксированную длину
  • Политика на случай переполнения очереди определена заранее
  • Ведётся наблюдение за числом miss, числом drop и глубиной очереди
  • Обновление UI и агрегация логов вынесены в цикл с более низкой частотой

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

Важно последнее значениеВажна каждая записьДля логированияОчередь заполненаЧто защищаем?Отбросить старые элементы, оставить последнийОповещение / остановка / управление потоком вверхОтбросить старые элементы, фиксировать только число drop

4.3. Приоритеты / MMCSS / background mode

Базовое правило работы с приоритетами — не поднимать всё подряд. На обычной Windows лучше работает подход «поднимать только важные потоки, а фоновую работу честно понижать». Background mode — это механизм, который склоняет к низкому приоритету не только CPU, но и такие ресурсы, как I/O.

Разделить работуПотоки, чувствительные к срокамСохранение / отправка / сжатие / агрегацияUIПри необходимости повышенный приоритет или MMCSSbackground mode / пониженный приоритетОбычный приоритетНе начинать сразу с REALTIME_PRIORITY_CLASS

Чек-лист

  • Не все потоки установлены на высокий приоритет
  • Повышены только потоки, действительно критичные по времени
  • Фоновая работа — сохранение, отправка, сжатие, синхронизация — переведена в background mode
  • Для непрерывной буферной обработки (аудио, видео, захват, воспроизведение) рассматривается MMCSS
  • Мышление в первую очередь строится по отдельным потокам, а не по процессу целиком
  • REALTIME_PRIORITY_CLASS не используется, пока необходимость не установлена чётко

MMCSS (Multimedia Class Scheduler Service) особенно эффективен для обработки вроде аудио / видео, где нужно «заполнять буфер за фиксированное время». Это лучше согласуется с архитектурой Windows, чем просто постоянный запуск потока с высоким приоритетом.

Код выглядит примерно так.

DWORD taskIndex = 0;
HANDLE avrt = AvSetMmThreadCharacteristicsW(L"Pro Audio", &taskIndex);
if (!avrt)
{
    throw std::runtime_error("AvSetMmThreadCharacteristicsW failed");
}

// Прогоняем цикл, чувствительный ко времени

if (!AvRevertMmThreadCharacteristics(avrt))
{
    throw std::runtime_error("AvRevertMmThreadCharacteristics failed");
}

4.4. Память / GC / затраты первого запуска

Если в горячем пути на каждой итерации использовать new / malloc / List<T>.Add / конкатенацию строк / LINQ, рано или поздно на первый план выйдут особенности сборки мусора и перераспределения памяти. Сама по себе GC (сборка мусора) не является злом, но если писать код с большим количеством выделений, его влияние проявится как джиттер.

ЗапускВыделить нужные буферыОдин раз коснуться, чтобы прогреть страницыЗавершить JIT / загрузку DLL / первый I/O заранееЗатем — реальные измерения / реальная работа

Чек-лист

  • В горячем пути нет выделения / освобождения памяти на каждой итерации
  • Нужные буферы выделены заранее при запуске
  • При запуске страницы один раз прогреты обращением
  • Первый JIT, первая загрузка DLL, первый I/O не попадают в реальные измерения
  • В цикле не растут огромные структуры или логи переменной длины
  • Если VirtualLock всё же используется, то только для очень небольшой критической области

Проверки для .NET

  • Для измерения времени используется Stopwatch / Stopwatch.GetTimestamp()
  • В горячем пути нет LINQ, конкатенации строк, ToString(), генерации больших логов
  • async/await не заносится в горячий путь
  • Оценка до и после прогрева выполняется раздельно

4.5. Настройки питания / EcoQoS / разрешение таймера

Этот раздел выглядит неброско, но эффективен. Даже если довести код до идеала, результат не будет стабильным, если сверху сильно давит управление питанием.

Питание на обычной WindowsРабота от сети переменного токаРежим питания: ближе к максимальной производительностиПри необходимости — отдельный план питания для боевой средыДержать чувствительные ко времени процессы подальше от EcoQoSПроверить, как обрабатываются запросы на разрешение таймера

Чек-лист

  • Боевая оценка сначала выполняется при питании от сети переменного тока
  • [Параметры] > [Система] > [Питание и батарея] > [Режим питания] установлен ближе к максимальной производительности
  • Во время работы не используется battery saver / режим с приоритетом энергосбережения
  • Проверены фирменные утилиты производителя — их тихий / eco / battery-приоритетный режимы
  • Чувствительные ко времени процессы неосторожно не помещены в EcoQoS (QoS с приоритетом энергосбережения)
  • IGNORE_TIMER_RESOLUTION не включён на стороне чувствительного ко времени процесса
  • Проверено, не меняется ли действие запроса на разрешение таймера при сворачивании / скрытии окна
  • Настройки питания для повседневного использования отделены от настроек для боевой среды / измерений / демонстраций

timeBeginPeriod полезен, если использовать его аккуратно, но это не панацея.

  • вызывайте его непосредственно перед необходимостью
  • по завершении возвращайте исходное значение через timeEndPeriod
  • начиная с Windows 10 версии 2004 он больше не ведёт себя настолько глобально, как раньше
  • в Windows 11 для процесса с окном, которое полностью скрыто / свёрнуто / не видно / не слышно, высокое разрешение может не гарантироваться
  • повышение разрешения не увеличивает точность QPC

Если есть подозрение на влияние питания или QoS, состояние power throttling можно проверить через SetProcessInformation.

PROCESS_POWER_THROTTLING_STATE state{};
state.Version = PROCESS_POWER_THROTTLING_CURRENT_VERSION;
state.ControlMask =
    PROCESS_POWER_THROTTLING_EXECUTION_SPEED |
    PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION;
state.StateMask = 0; // HighQoS (с приоритетом производительности) + учитывать запросы на разрешение таймера

if (!SetProcessInformation(
        GetCurrentProcess(),
        ProcessPowerThrottling,
        &state,
        sizeof(state)))
{
    throw std::runtime_error("SetProcessInformation failed");
}

4.6. Размещение по CPU / миграция между ядрами / нагрев

В размещении по CPU часто лучше работает не резкая привязка к конкретным ядрам (hard affinity / CPU pinning), а начало с указания, близкого к soft affinity — «желательно работать преимущественно на этой группе ядер».

ДаНетСначала измеритьSetThreadIdealProcessor / CPU SetsУлучшение достаточное?Остановиться на этомВ последнюю очередь рассмотреть SetThreadAffinityMaskОдновременно проверять температуру / частоту / долгую работу

Чек-лист

  • Размещение по CPU меняется только после предварительного измерения
  • Нет резкой привязки к конкретным ядрам с самого начала
  • Сначала пробуются SetThreadIdealProcessor или CPU Sets
  • SetThreadAffinityMask рассматривается как крайняя мера
  • При долгой работе проверяются температура, частота, термотроттлинг
  • Проверены тихий режим и режим пониженного шума для портативных ПК

В качестве последовательности безопасен такой порядок.

  1. сначала измерить
  2. при необходимости — ideal processor / CPU Sets
  3. если улучшение всё ещё нужно — привязка к конкретным ядрам

Привязка к конкретным ядрам выглядит эффективной, но она сокращает пути отступления для ОС, поэтому при неосторожном использовании может, наоборот, сделать систему менее гибкой.

4.7. Разделение драйверов / DPC / ISR / внешних помех

Когда «иногда резко подскакивает только max» или «среднее хорошее, а p99.9 плохой», стоит заподозрить внешние помехи за пределами кода приложения.

ДаНетДаНетДаНетДаНетВозник всплеск late / miss / maxСобственное время обработки тоже велико?Сократить горячий путь / уменьшить выделения / убрать I/OЕсть всплески DPC / ISR?Проверить USB / Wi-Fi / Bluetooth / GPU / аудио / хранилище / ACPI / обновления драйверовЕсть page fault / GC / затраты первого запуска?Предварительное выделение / прогрев / снижение нагрузки на кучуЕсть влияние батареи / энергосбережения / нагрева?Питание от сети / настройки питания / охлаждение / длительные тестыУглубиться с помощью ETW / WPA / LatencyMon

Чек-лист

  • Проверены драйверы вокруг Wi-Fi / Bluetooth / USB / хранилища / GPU / аудио
  • Проведено сравнение с отключёнными ненужной облачной синхронизацией, индексированием, автообновлениями
  • Проверено, ломается ли всё при сворачивании окна или выключении экрана
  • Тенденции DPC / ISR просмотрены через LatencyMon или ETW
  • Раздельно рассмотрено, «моя обработка тяжёлая» или «меня останавливают извне»

5. Измерение и оценка

5.1. Что записывать

Как минимум стоит фиксировать следующее.

  • запланированное время цикла
  • фактическое время начала
  • фактическое время завершения
  • lateness (насколько позже запланированного начала фактически стартовало)
  • время выполнения
  • число пропущенных сроков (missed deadline)
  • число подряд идущих пропущенных сроков
  • глубину очереди
  • число drop
  • загрузку CPU
  • перекос по ядрам
  • всплески DPC / ISR
  • page fault
  • колебания температуры / частоты

Если смотреть только на среднее значение, суть уловить трудно. В реальной эксплуатации проблемой становятся именно редкие крупные всплески задержки.

5.2. Как читать p99 / p99.9 / max

Показатели вроде p99 существуют для того, чтобы смотреть на медленный «хвост». При взгляде только на среднее редкие крупные задержки остаются скрытыми.

Метрика Значение Интуиция при 10 000 измерений
Среднее Сглаженное общее значение Всплески легко теряются
p50 Срединное значение Близко к обычному ощущению
p95 Граница, где начинают быть видны самые медленные 5% Граница, исключающая самые медленные 500
p99 Граница, где начинает быть видно самое медленное 1% Граница, исключающая самые медленные 100
p99.9 Граница, где начинают быть видны самые медленные 0,1% Граница, исключающая самые медленные 10
max Худший случай Единственный самый медленный результат

Например, при:

  • среднем: 0,8 мс
  • p99: 1,2 мс
  • p99,9: 3,5 мс
  • max: 28 мс

это означает: обычно быстро, но иногда случаются крупные всплески. На обычной Windows реальная проблема почти всегда проявляется именно в этом хвосте от p99 до max.

5.3. Чем измерять

Набор инструментов в целом стандартный.

  • Измерение внутри приложения Сначала собрать самостоятельно period / lateness / execution time / queue depth / drop
  • ETW / WPR / WPA Углубиться в CPU, переключения контекста, DPC / ISR, page fault
  • LatencyMon Прицелиться в колебания, вызванные драйверами
  • Мониторинг температуры / частоты Следить за влиянием нагрева
Измерение внутри приложенияp50 / p95 / p99 / p99.9 / maxmiss / drop / глубина очередиETW / WPR / WPAпереключения контекста / DPC / ISR / page faultМониторинг температуры / частотыОпределить приоритеты улучшений

Дойти до WPA несколько трудозатратно, но это весьма эффективно для того, чтобы разделить, виноваты ли DPC / ISR или просто собственная обработка слишком тяжёлая.

5.4. Дисциплина тестирования

Одного тихого стендового окружения для тестирования недостаточно. Стоит как минимум раздельно рассмотреть такие условия.

  • сразу после запуска, до прогрева
  • после прогрева
  • длительная непрерывная работа
  • UI на переднем плане
  • UI свёрнут / состояние, близкое к скрытому
  • питание от сети переменного тока
  • питание от батареи
  • состояние с нагрузкой на сеть или диск

Оценка только на стенде легко приводит к тому, что проблемы, возникающие в реальной эксплуатации, остаются незамеченными. Поведение обычной Windows легко тянется за тем, «как её используют», поэтому важно проверять в условиях, приближенных к реальному использованию.

6. Краткое руководство по выбору

  • Класс 10–20 мс, редкие колебания можно поглотить → разделение fast / slow, очередь фиксированной длины, обычный — чуть повышенный приоритет, событийная модель часто уже достаточны

  • Класс 1–5 мс, нужно непрерывно укладываться → горячий путь без выделений памяти, выделенные потоки, MMCSS или аккуратная настройка приоритетов, высокоточный waitable timer, питание от сети переменного тока, настройки питания ближе к максимальной производительности

  • Приближение к менее чем 1 мс, и нельзя промахиваться даже при долгой работе под высокой нагрузкой → одним лишь user-mode на обычной Windows это весьма тяжело. Стоит рассмотреть перенос критичной части в другое место

  • GUI / логи / связь / БД сосуществуют все вместе → не тащить всё в схему «один процесс, один цикл», а разделять зоны ответственности, так как особенности последующих этапов легко ломают сроки предыдущих

7. Итог

Стоит зафиксировать две предпосылки.

  • На обычной Windows цель — не гарантия hard real-time, а конфигурация soft real-time: небольшая задержка и джиттер, и отсутствие поломки при нарушении срока
  • Самый большой эффект даёт не настройка приоритетов, а наведение порядка в горячем пути

На стороне реализации эффективны такие вещи.

  • разделять fast path и slow path
  • использовать очередь фиксированной длины и заранее решить политику на случай переполнения
  • измерять через QPC, ждать через события / waitable timer
  • в горячем пути избегать выделений памяти, блокирующего I/O, тяжёлых блокировок

На стороне эксплуатации эффективны такие вещи.

  • работать при питании от сети переменного тока
  • держать отдельные настройки питания для боевой среды
  • сокращать ненужную фоновую нагрузку
  • оценивать по p99 / p99.9 / max и числу пропусков

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

8. Источники

Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.

Эти страницы показывают тему статьи в более широком контексте услуг и решений.

Частые вопросы

Вопросы, которые часто возникают при консультациях по теме статьи.

Можно ли выполнять обработку реального времени на Windows?
Гарантировать hard real-time (нулевые нарушения сроков) невозможно, но если тщательно проработать проектирование, реализацию, измерения и эксплуатацию, можно довести систему до вполне практичного состояния soft real-time. Периодическая обработка с интервалом от нескольких миллисекунд до нескольких десятков миллисекунд, буферный режим для аудио/видео, сбор данных с датчиков и циклы управления — всё это вполне реалистично даже на обычной Windows 10/11. И наоборот, если нужна гарантия нулевых нарушений сроков или стабильность на уровне сотен микросекунд и меньше в течение долгого времени, стоит рассмотреть перенос на RTOS, выделенный контроллер, FPGA или обработку на стороне устройства.
Почему нельзя использовать Sleep(1) в периодической обработке?
Потому что Sleep(1) даёт не «период в 1 мс», а поведение вида «подождать обычно чуть больше 1 мс и добавить сверху время обработки», а перерасход ожидания накапливается без изменений. Стабильнее прогонять периодический цикл по абсолютному сроку next += period, отдавать предпочтение ожиданию через события устройства или высокоточный waitable timer, а короткий busy-spin оставлять только для финальной подстройки. timeBeginPeriod следует использовать только на время реальной необходимости, а затем возвращать исходное значение.
Что сильнее всего помогает сократить задержку и джиттер?
Не повышение приоритета, а то, чтобы сделать горячий путь коротким, фиксированной длины и неблокирующим. Fast path (получение / управление) и slow path (сохранение / связь / UI) разделяют и соединяют через очередь фиксированной длины, а в горячем пути избегают записи в файл, сетевой отправки, тяжёлого логирования и выделения памяти на каждой итерации. На стороне эксплуатации помогают питание от сети переменного тока, пересмотр режима питания, проверка EcoQoS и наведение порядка в фоновой нагрузке.
По каким показателям оценивать стабильность периодической обработки?
Не только по среднему значению, а по p99, p99.9, max и числу пропущенных сроков (missed deadline). Например, если среднее — 0,8 мс, а максимум — 28 мс, это означает, что обычно всё быстро, но иногда случаются крупные всплески, и на обычной Windows реальная проблема как раз проявляется в этом «хвосте» от p99 до max. Дополнительно стоит фиксировать всплески DPC/ISR, page fault, глубину очереди и колебания температуры/частоты, а оценку проводить раздельно по условиям: до и после прогрева, при длительной работе, в свёрнутом состоянии, при питании от батареи и т. д.

Об авторе

Страница с профилем автора статьи.

Го Комура

Представитель KomuraSoft LLC

Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.

Публичные ссылки

Вернуться в блог