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 на всех потоках сразу, из-за чего система перегревается и снижает частоту.
Связанные понятия
Полезные ресурсы
🔤 Английские термины в этой теме
Слова из этой темы, которые русскоязычные часто произносят неверно. Кнопка 🔊 произносит голосом браузера.
- maintenance /ˈmeɪntənəns/ первый, «мейнтэнэнс», не «мейнтЕнанс» разбор: Немые буквы и ударение: cache, queue, suite, height
- thread /θrɛd/ с «θ» разбор: Типичные ошибки русскоязычных: th, w/v, долгота гласных
- kernel /ˈkɜːrnəl/ «кёрнел», не «кернел» разбор: Английский Linux и терминала
- written /ˈrɪtn/ «ритн», двойное t произносится как одно, i краткое разбор: Письменная коммуникация