Async Logging Is Not a Silver Bullet — What Actually Limits Performance

WILD

Administrator
Staff member
ADMIN
SELLER
SUPREME
MEMBER
Joined
Jan 21, 2025
Messages
219
Reaction score
637
Deposit
0$

Почему перенос логирования в отдельный поток не делает его дешевле — и куда на самом деле уходят затраты.​

Асинхронное логирование часто рассматривается как очевидная оптимизация.

Нет.

Это просто переносит затраты в другое место.

Эта идея кажется простой: синхронное логирование блокируется, а асинхронное — нет, следовательно, оно должно быть быстрее.

Но если взглянуть на то, что на самом деле происходит внутри системы, картина становится совершенно иной.

Библиотеки, подобные Quill, построены на основе асинхронных конвейеров. Другие, например spdlog , поддерживают как синхронный, так и асинхронный режимы. Некоторые системы, включая logme , намеренно смешивают синхронное форматирование с асинхронным выводом.

Несмотря на эти различия, все они сталкиваются с одними и теми же фундаментальными ограничениями.

Асинхронное логирование не устраняет затраты на логирование. Оно просто перемещает данные между процессами.
Работа по-прежнему существует: форматирование, копирование, синхронизация, ввод-вывод. Вопрос лишь в том, где и когда за неё будут платить.

Это не критика асинхронного логирования как концепции. Асинхронное логирование — мощный инструмент, если использовать его по назначению. Оно может повысить скорость отклика и изолировать медленные выходные данные от критически важных путей. Проблема не в самом асинхронном режиме, а в предположении, что он автоматически делает логирование дешевле. Это не так — он лишь перераспределяет затраты.

Дело не в чём-то одном.​

То, что обычно называют «асинхронным логированием», на самом деле представляет собой объединение двух совершенно разных механизмов.

Одна часть касается подготовки данных, другая — способа их предоставления.

Это различие становится очевидным, если взглянуть на реальные реализации. В библиотеке логирования Quill значительная часть системы посвящена безопасной передаче аргументов между потоками. В синхронных конфигурациях spdlog форматирование и вывод остаются в вызывающем потоке, и проблема просто исчезает.

Вывод можно перенести в другой поток. Однако
избежать подготовки данных невозможно.

Отложенное форматирование не бесплатно.​

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

Такой подход привлекателен и широко используется. Но он работает только при одном условии: данные должны оставаться действительными на момент их обработки бэкэндом.

И вот здесь-то всё начинает рушиться.

std::string s = MakeText();
std::string_view sv = s;
LOG_INFO("{}", sv);
s.clear();


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

Это не единичный случай. Это фундаментальное ограничение отложенной обработки.

Что на самом деле делают системы?​

Если принять во внимание проблему продолжительности жизни, то множество возможных решений становится на удивление малым.

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

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

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

На этом этапе конвейер обработки данных уже не так прост, как кажется.

Конвейер гораздо длиннее, чем кажется.​

Асинхронное логирование часто представляют следующим образом:

добавить в очередь → формат → вывод


В реальности это выглядит так:

захват → добавление в очередь → форматирование → вывод


Этот этап сбора данных не является необязательным. Он необходим для корректной работы.

А если учесть этот фактор, преимущество отложенного форматирования становится менее очевидным. Во многих случаях копирование с отложенным форматированием оказывается сопоставимым с немедленным форматированием.

Работа продолжается.

Примечание о реальных измерениях​

Это не просто теория.

Если вы посмотрите на общедоступные тесты производительности, сравнивающие spdlog, Quill и другие библиотеки (см. эту статью: « Чего на самом деле стоит LOG_INFO()? Тестирование библиотек логирования на C++» ), то увидите одну закономерность:

Форматирование часто составляет основную часть затрат на ведение журналов.
Это особенно заметно в синхронных конфигурациях, где форматирование происходит непосредственно в вызывающем потоке, и его стоимость нельзя скрыть за очередями или фоновыми рабочими процессами.

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

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

Асинхронный вывод не увеличивает пропускную способность.​

Перенос вывода в отдельный поток полезен. Это позволяет отделить вызывающую функцию от задержки ввода-вывода и уменьшить задержки.

Но это не ускоряет ввод-вывод.

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

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

Однопоточное узкое место​

Многие асинхронные системы логирования используют один поток на стороне сервера.

Это упрощает проектирование, но также создает жесткое ограничение. Все сообщения в конечном итоге проходят через одного потребителя.

Хуже того, затраты на приемники данных накапливаются. Запись на несколько выходов увеличивает общий объем работы на одно сообщение. Медленный приемник данных замедляет все.

Это структурное свойство, а не деталь реализации.

Перегрузка не исчезает​

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

Буферы увеличиваются. Производители блокируются. Сообщения теряются.

Асинхронное логирование не устраняет перегрузку. Оно определяет, как будет вести себя перегрузка.

Альтернативная точка зрения​

На этом этапе другой подход становится более интересным.

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

Затем перенесите только выходную часть на асинхронные бэкэнды.

Это позволяет избежать проблем, связанных со сроком службы файлов, уменьшает необходимость копирования и упрощает систему. Такой подход также встречается в реальных системах, включая logme, где форматирование рассматривается как операция с высокой нагрузкой и оптимизируется соответствующим образом, в то время как ввод-вывод обрабатывается асинхронно.

Важен не конкретный тип библиотеки, а сама идея:

Асинхронный вывод без асинхронного форматирования часто является более предсказуемым вариантом проектирования.

Заключительная мысль​

Асинхронное логирование часто преподносится как способ удешевить процесс логирования.

Нет.

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

Если взглянуть на это с такой точки зрения, пространство для проектирования становится гораздо яснее.
 
Top Bottom