Low-latency C++: код с минимальной задержкой

Тема дорожной карты · Modern C++ Developer

Low-latency C++ — дисциплина написания кода, где важна не средняя пропускная способность, а худшая задержка отдельной операции: микросекунды от прихода сетевого пакета до отправки ответа. Так пишут торговые системы, матчинг-движки бирж, аудиодвижки и промышленные контроллеры. Язык здесь выбирают за предсказуемость: нет сборщика мусора, нет JIT, каждая аллокация и каждое обращение к памяти видны разработчику и поддаются контролю.

Как это работает

Главный принцип — убрать из горячего пути всё, что имеет непредсказуемое время. Динамические аллокации заменяют пулами и аренами, подготовленными на старте. Системные вызовы и блокировки исключают: потоки общаются через lock-free очереди на кольцевых буферах, а вместо мьютексов используют атомарные операции с явным порядком памяти. Потоки прибивают к ядрам процессора через pthread_setaffinity_np, а сами ядра изолируют от планировщика параметром isolcpus, чтобы ничто постороннее не вытесняло обработчик.

Вторая группа приёмов касается памяти и кэша. Данные раскладывают так, чтобы горячие поля лежали в одной кэш-линии, а поля, которые пишут разные потоки, разносят по разным линиям, избегая false sharing. Ветвления в горячем цикле минимизируют или подсказывают компилятору через [[likely]], виртуальные вызовы заменяют шаблонами и CRTP. Вместо ожидания события через epoll поток крутится в busy-wait, потому что пробуждение из сна стоит десятки микросекунд.

Третья группа — обход ядра. Сетевой стек Linux добавляет от 5 до 20 микросекунд, поэтому применяют DPDK, Solarflare OpenOnload или собственные драйверы, читающие пакеты прямо из памяти сетевой карты.

Когда применять

Такой подход оправдан, когда бизнес-метрика напрямую зависит от хвоста задержек: p99 и p99.9, а не среднего. В высокочастотной торговле опоздание на микросекунду означает, что сделку заберёт конкурент. В профессиональном аудио превышение бюджета кадра слышно как щелчок. Если же система обрабатывает запросы за миллисекунды и пользователь не заметит разницы, low-latency-приёмы только усложнят код и затруднят сопровождение — обычного идиоматического C++ будет достаточно.

Типичные ошибки

Первая ошибка — оптимизировать без измерений на реальном железе с включёнными таймстампами сетевой карты; счётчик rdtsc без калибровки и фиксированной частоты даёт мусор. Вторая — скрытые аллокации: std::string, std::function, исключения и даже std::vector::push_back в горячем пути незаметно зовут malloc. Третья — игнорирование ядра операционной системы: без изоляции ядер, отключения прерываний и huge pages код с идеальной микрооптимизацией всё равно получит выброс задержки от планировщика. Четвёртая — busy-wait на всех потоках сразу, из-за чего система перегревается и снижает частоту.

Связанные понятия

Полезные ресурсы

🔤 Английские термины в этой теме

Слова из этой темы, которые русскоязычные часто произносят неверно. Кнопка 🔊 произносит голосом браузера.

Весь словарь терминов →