shuttle_async: lock-free планировщик корутин на C++23 ускоряет задачи в 25х

· ·

shuttle_async: lock-free планировщик корутин на C++23 ускоряет задачи в 25х

PrimeCoder · 2026 · формат: tech

Разбор новинки простыми словами: что это, кому нужно и брать ли.

Показывает разработчикам, как бесстековые корутины C++23 и work stealing на деках Chase-Lev дают почти линейное масштабирование без мьютексов и Thundering Herd.

Связанные услуги: каталог, AI Boost Team, стать клиентом.

25.2х–26.2х на 31 потоке. Цифра звучит как рекламный баннер, пока не посмотришь, откуда она. Автор под ником shagunov26 выложил разбор shuttle_async — экспериментального планировщика задач на бесстековых корутинах C++23. Библиотека не продаётся и не продвигается: это изолированное вычислительное ядро для собственного движка ShuttleEngine на Vulkan API. И именно поэтому разбор стоит внимания — там показана механика, а не обещания.

Для владельца бизнеса здесь нет готового продукта. Есть сигнал: планка «нормальной» многопоточности в нативных проектах сдвинулась. То, что пять лет назад писали через пул потоков и мьютексы, в 2026-м упаковывают в lock-free планировщик на деках Chase-Lev. Если у вас в команде есть C++ и задачи с жёстким бюджетом кадра или отклика — это ваш текст. Если нет — читайте как маркер рынка: стоимость «правильной» асинхронности падает.

Что это такое, если убрать сишные детали

ShuttleEngine — графический движок на Vulkan. Vulkan снимает ограничения старых конвейеров и даёт контроль над GPU, но всю многопоточность на CPU перекладывает на разработчика. Автор формулирует прямо: рендерер приходится параллелить целиком — сборка командных буферов, стриминг текстур и мешей с диска в VRAM, физика, анимации, culling.

Разработка собственного графического движка на Vulkan API неизбежно упирается в многопоточность: параллельную сборку командных буферов, стриминг мешей и генерацию кадров.

Под это и написан shuttle_async. Внутри — бесстековые корутины C++23. Такие корутины по умолчанию «дикие»: они не привязаны к потокам ОС. Чтобы подчинить их игровому циклу, в рантайме есть абстракция IExecutor — экзекуторы решают, где и когда корутина получит управление. Переключение между корутинами идёт через Symmetric Transfer на уровне jmp-переходов, без обращения к планировщику операционной системы.

Чем это отличается от привычного пула потоков

Классический путь: std::jthread, std::mutex, std::condition_variable, очередь задач. В бюджет кадра 16.6 мс для 60 FPS (или 7.1 мс для 144 FPS) такой подход быстро упирается в ожидание блокировок. Мьютекс на критическом пути тянет за собой переключение контекста ядра ОС, а это в игровом цикле стоит непомерно дорого.

Второй классический враг — Thundering Herd. Одно уведомление condition_variable будит весь пул, и потоки начинают драться за атомики, сжигая CPU на пустую работу. В shuttle_async это лечат Coalesced Wake-ups: несколько пробуждений схлопываются в одно.

Третий слой — балансировка. Планировщик децентрализованный: у каждого воркера свой локальный дек Chase-Lev, плюс есть глобальная ConcurrentQueue. Загруженный поток не «зовёт на помощь» — свободные воркеры сами приходят и разбирают его дек. Мьютексов на критических путях нет вообще.

Что показали замеры

Стресс-тест собран в Release с -O3, без логирования и диагностики, чтобы мерить чистую пропускную способность. Железо — Intel Core i9-14900KF: 24 ядра, 32 потока. Три профиля нагрузки:

  • Малые задачи. 50 000 корутин по 10 000 легковесных итераций. Проверка наносекундного оверхеда планирования и устойчивости очередей к драке за атомики.
  • Большие задачи. 5 000 корутин по 4 000 000 тяжёлых итераций. Чистый compute: сколько сырой мощности кремния выжимает рантайм, включая разнородные P- и E-ядра.
  • Неравномерные задачи. 10 000 корутин в хаотичной пропорции: 10% по 8 000 000 итераций, 90% по 50 000. Эмуляция реального кадра, где лёгкая логика сущностей перемешана с тяжёлым рендерингом и физикой.

Кривая масштабирования распалась на три зоны. До 8 воркеров — линейный рост, а на 4 воркерах в «малых» и «неравномерных» профилях ускорение перескочило математический предел: 4.40х и 4.05х. Объяснение физическое: корутины расходятся по независимым P-ядрам, их данные целиком укладываются в локальные L1/L2, обращений к общей L3 и RAM нет. На 16 воркерах ОС подключает Hyper-Threading на тех же физических ядрах, и рантайм держит 13.5х–14.5х. Дальше в игру входят E-ядра, и на 31 потоке система выходит на пик 25.2х–26.2х.

Кому это нужно и как применить

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

Второй — те, кто тонет в коллбэках:

Писать асинхронный код на цепочках коллбэков (Callback Hell) в 2026 году — инженерное преступление.

Практический вывод для руководителя: если в проекте асинхронность держится на мьютексах и ручных пулах потоков, узкое место часто не в алгоритмах, а в переключениях контекста и балансировке. Бесстековые корутины с work stealing — это способ вытащить производительность без переписывания логики задач.

Где подвох

Подвох первый — железо. Все замеры на одном гибридном процессоре Intel, и поведение там объясняется кэшами и тем, как Windows распределяет P- и E-ядра. На AMD, на серверных Xeon, на старых CPU картина будет другой. Переносить 25х на свой парк без собственного прогона — самообман.

Подвох второй — плато. Оно наступает уже на 32 воркерах:

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

Подвох третий — зрелость. Автор честно разбирает архитектурный баг синхронного инлайна, который едва не сломал инварианты Structured Concurrency. Это нормальный признак живой библиотеки, но и напоминание: такие ядра обкатывают годами. Плюс C++23 — не тот стандарт, который стоит на каждом проекте.

Брать или подождать

Брать — если вы пишете свой движок или CPU-bound сервис с дедлайнами и готовы держать в команде человека, который понимает корутины, атомики и memory ordering. Архитектурные идеи (локальные деки, Coalesced Wake-ups, отказ от мьютексов на горячем пути) переносятся и без копирования кода.

Подождать — если нужен готовый продакшн-компонент с гарантиями, SLA и поддержкой. Библиотека живёт внутри одного проекта, тестировалась на одной конфигурации, охват публикации на момент выгрузки — 789 читателей. Для бизнес-критичного контура этого мало.

Вердикт

Это инженерный текст, а не релиз продукта. Ценность — в разборе механики, которая объясняет, откуда берётся почти линейное масштабирование и почему мьютекс на горячем пути дороже, чем кажется. 25х на 31 потоке — личный рекорд автора на конкретном i9. Ваш результат будет другим, и узнать его можно только замером.

FAQ

Можно ли применить это в обычном бэкенде?

Идеи — да, код — с оговорками. Бесстековые корутины и work stealing полезны там, где много коротких задач и есть дедлайн. Для CRUD-нагрузки выигрыш будет в пределах шума.

Что такое деки Chase-Lev простыми словами?

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

25х — это про любой код?

Нет. Это про три синтетических профиля на i9-14900KF в Release с -O3. Реальная нагрузка почти всегда грязнее.

А баг со Structured Concurrency критичен?

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

Если у вас в проекте есть узкое место на многопоточности — напишите в комментариях, какая нагрузка: короткие задачи, тяжёлый compute или смешанная. Разберём на цифрах, а не на лозунгах.

Читайте также

Комментарии (1)

  • PrimeCoder Team · Официальный ответ · PrimeCoder

    Опишите, кто сейчас обрабатывает заявки и какие SLA по факту — предложим поэтапный сценарий без остановки операционки.

Обсудить на сайте