Без аллокаций и без компромиссов: десятичная арифметика, которая меняет правила HFT на Go
Скрытый враг производительности в Go-системах для торговли
Каждый Go-разработчик знаком с этой ситуацией: ты довёл до ума горячие участки кода, вычистил лишние аллокации — и тут случается катастрофа. Одна операция с десятичным числом запускает цепочку выделений памяти в куче, которая в самый неподходящий момент вызывает сборщик мусора.
Именно так выглядит реальность для многих, кто работает с финансовыми системами, торговыми платформами и любыми приложениями, где важна точность, но нельзя жертвовать скоростью.
Почему стандартные решения не подходят
Встроенные типы с плавающей точкой в Go — и в любом другом языке — вещь предсказуемая. Для большинства задач их точности хватает, но по сути это двоичное представление десятичных значений. Когда нужна абсолютная точность вычислений — а в финансах это критично — начинаются проблемы.
Многие обращаются к библиотекам типа shopspring/decimal. Отличная библиотека, ничего не скажешь. Но она намеренно выделяет память при каждой операции с числами.
Представьте торговый движок, обрабатывающий тысячи ордеров в секунду:
- Каждый расчёт цены — новые аллокации
- Сборщик мусора рано или поздно срабатывает
- Задержки возникают в самые неудобные моменты
- P99-латенси становятся непредсказуемыми
Для обычного веб-приложения это допустимо. Для высокочастотного трейдинга, где микросекунды напрямую конвертируются в деньги — это катастрофа.
Zero-Allocation: красивая идея и суровые требования
Концепция простая: все вычисления без выделения памяти в куче. Каждая операция работает либо с данными на стеке, либо с заранее подготовленными буферами. Результат — предсказуемая, стабильная производительность без пауз на сборку мусора.
Реализовать это на практике непросто:
- Фиксированный размер представления чисел, где это возможно
- Операции, изменяющие данные на месте, а не возвращающие новые
- Внимательное планирование обработки переполнений и точности
- Полное отсутствие мест, где может возникнуть паника при штатной работе
Вот этот момент с паниками особенно важен для торговых систем. Одна паника в горячем пути способна привести к пропущенным сделкам, сорванным транзакциям или ещё более неприятным последствиям. Надёжные системы обрабатывают граничные случаи аккуратно.
Почему это важно не только для HFT
Да, маркетологи любят слова «производительность уровня HFT», но выводы гораздо шире:
Серверы игр, где балансы игроков требуют точного расчёта без лагов в часы пик.
E-commerce платформы, обрабатывающие огромные объёмы транзакций во время распродаж и праздников.
Дашборды реального времени, где вычисление метрик должно успевать за потоком входящих данных.
Платёжные системы, где стабильная латентность напрямую влияет на пользовательский опыт и конверсию.
Базовый принцип — устранение лишних аллокаций в горячих участках — применим повсюду в системном программировании.
Почему Go подходит для этого лучше других
Философия Go делает техники zero-allocation более доступными, чем во многих языках. Явная обработка ошибок заставляет продумывать граничные случаи. Семантика передачи по значению используется по умолчанию, что уменьшает случайные аллокации в куче. А инструменты вроде pprof делают точки аллокации видимыми ещё на этапе разработки.
Библиотеки, использующие этот подход, отражают взросление Go-экосистемы для задач с жёсткими требованиями к производительности. Мы видим всё больше специализированных решений, которые делают другие компромиссы, чем универсальные библиотеки. Это позволяет разработчикам выбирать инструменты под свои конкретные ограничения.
Что дальше
По мере роста использования Go в финансовом секторе и торговой инфраструктуре стоит ожидать появления новых библиотек с фокусом на определённые характеристики производительности. Времена фраз «возьми big.Float» или «произвольная точность — это нормально» уходят. Им на смену приходит более вдумчивый подход, учитывающий разнообразие требований.
Для разработчиков систем, где важны миллисекунды — а то и наносекунды — арифметика десятичных чисел без аллокаций это не роскошь. Это необходимость.
Сталкивались ли вы с проблемами производительности при работе с десятичными числами в Go? Расскажите о своём опыте в комментариях.