Логирование есть практически в каждом C++ проекте. Почти любой сервис, демон или библиотека рано или поздно обрастает строками вроде LOG_INFO(...) или logger.debug(...).
Чаще всего библиотека выбирается по привычке или популярности — spdlog, quill, easylogging++ и т.п. При этом редко кто проверяет, какую цену приложение платит за логирование.
В высоконагруженных системах логирование может выполняться:
миллионы раз в секунду
из разных потоков
с форматированием строк
с записью в файл или консоль
В этот момент библиотека логирования начинает попадать в критический путь выполнения программы.
Со временем возникает довольно практичный вопрос:
Сколько на самом деле стоит один вызов LOG_INFO?
Чтобы получить более конкретный ответ, был написан небольшой benchmark.
Исходники теста открыты:
https://github.com/efmsoft/logbench
Любой может собрать его и воспроизвести результаты.
Что именно тестируется
Benchmark измеряет максимальное количество сообщений логирования в секунду.
Проверяются четыре сценария:
сценарий | описание |
|---|---|
null | логирование отключено |
file | запись в файл |
console | вывод в консоль |
file + console | запись в файл и консоль |
Сценарий null особенно интересен.
Он показывает стоимость самого вызова логирования, когда сообщение фактически не выводится.
То есть измеряется overhead библиотеки.
Какие библиотеки участвовали
В тестировании участвовали следующие библиотеки (со ссылками на репозитории):
logme — https://github.com/efmsoft/logme
spdlog — https://github.com/gabime/spdlog
quill — https://github.com/odygrd/quill
easylogging++ — https://github.com/amrayn/easyloggingpp
Для logme дополнительно протестированы разные API форматирования:
C-style (
printf)std::formatiostream
Это позволяет отдельно посмотреть влияние форматирования и понять, насколько сильно выбор API влияет на итоговую производительность.
Конфигурация тестовой системы
Тестирование выполнялось на следующей системе:
CPU: 13th Gen Intel(R) Core(TM) i9‑13900HX (2.20 GHz)
OS: Windows 11 Home
Version: 25H2
Важно отметить, что представленные ниже результаты получены на релизной сборке benchmark.
Условия тестирования
Чтобы сделать сравнение максимально корректным, во всех библиотеках использовался минимальный формат вывода.
Из формата были исключены дополнительные поля, которые часто используются в реальных системах:
метки времени
thread id
уровень логирования
имя логгера
Целью было минимизировать влияние форматирования и дополнительных вычислений и тем самым максимально приблизить условия тестирования между разными библиотеками.
Параметры теста
По умолчанию benchmark запускается так:
--seconds=3 --repeat=5 --warmup-ms=300 --pause-ms=250
Для более стабильных результатов рекомендуется:
--seconds=15
Каждый тест выполняется несколько раз, после чего в качестве итогового значения используется median (медиана) полученных результатов. Такой подход позволяет уменьшить влияние случайных выбросов и нестабильности измерений.
Результаты
Ниже приведены результаты benchmark. Все значения — сообщения в секунду.
Library | null | file | console | file+console --------------------+--------------+--------------+--------------+------------- logme (c) | 527280306 | 142808726 | 615908 | 650303 logme (cpp-stream) | 30875385 | 26293202 | 637631 | 596787 logme (std::format) | 148024581 | 89936987 | 640175 | 575164 spdlog | 245775694 | 119288244 | 677708 | 621846 quill | 1225050 | 1620915 | 219747 | 231010 easylogging++ | 26779465 | 1654775 | 581394 | 394377
Почему null-benchmark вообще важен
На первый взгляд может показаться, что сценарий null не очень полезен: если логирование отключено, зачем вообще измерять его стоимость?
На практике именно этот сценарий часто оказывается критичным. Во многих проектах логирование вызывается повсеместно, но фактический вывод сообщений зависит от уровня логирования. Например:
LOG_DEBUG("Request id={}", id);
Даже если уровень DEBUG отключён, библиотека всё равно должна выполнить некоторую работу:
проверить уровень логирования
подготовить аргументы
выполнить часть логической обработки
Если этот путь реализован неэффективно, даже отключённые логи могут заметно влиять на производительность приложения.
Поэтому сценарий null позволяет измерить именно стоимость вызова логирования как такового, без влияния I/O.
Overhead логирования
Важно пояснить один момент: сценарий null не означает отключение логирования на уровне макросов (например через #define). В этом случае стоимость вызова была бы практически нулевой и такой тест был бы малоинтересен.
В benchmark измеряется ситуация, когда вызов логирования действительно происходит, но сообщение никуда не выводится:
в logme у канала отсутствуют backend'ы
в spdlog у логгера отсутствуют sink'и
Таким образом измеряется именно именно внутренний overhead библиотеки логирования: проверка уровня, подготовка контекста, обработка аргументов и прочие внутренние операции.

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

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

Интуитивно можно ожидать, что при записи в файл разница между библиотеками исчезнет — ведь основная стоимость должна быть I/O.
Но результаты показывают, что архитектура библиотеки всё ещё играет значительную роль.
На производительность влияют:
буферизация
блокировки
стратегия записи
организация backend
Вывод в консоль

При выводе в консоль различия уменьшаются.
Причина довольно очевидна — узким местом становится сама консоль.
Поэтому большинство библиотек показывают похожие результаты.
File + Console

Комбинированный сценарий показывает примерно ту же картину.
Когда консоль участвует в выводе, она начинает ограничивать общую производительность.
Влияние форматирования
Отдельно интересно посмотреть на влияние API форматирования. На практике именно форматирование часто оказывается одной из самых дорогих операций в логировании.
API | file msgs/sec |
|---|---|
C-style | 19M |
std::format | 6.7M |
iostream | 4.1M |
Получается:
std::formatпримерно в 3 раза медленнееiostreamпримерно в 4–5 раз медленнее
Если приложение активно логирует, это становится заметным.
Почему результаты отличаются
Производительность логирования зависит от нескольких факторов.
Архитектура библиотеки
Некоторые библиотеки используют:
mutex
lock‑free очереди
буферизацию
Каждый подход имеет свои компромиссы.
Проверка уровня логирования
Лучший вариант — проверять уровень как можно раньше, чтобы избежать лишней работы.
Форматирование строк
Форматирование часто оказывается одной из самых дорогих операций.
Особенно если используются iostream или сложные formatter'ы.
Буферизация вывода
Если каждое сообщение сразу записывается в файл, это значительно медленнее.
Почему у вас результаты могут отличаться
Benchmark чувствителен к:
CPU
файловой системе
терминалу
компилятору
настройкам оптимизации
Поэтому абсолютные значения могут отличаться.
Но относительная картина обычно остаётся похожей.
Как воспроизвести тест
git clone https://github.com/efmsoft/logbench
cmake -B build cmake --build build --config Release
logbench --seconds=15
Итоги
Несколько наблюдений после тестирования:
Стоимость логирования может отличаться на порядки.
Даже отключённые логи могут иметь заметный overhead.
Форматирование строк влияет сильнее, чем часто ожидается.
Архитектура библиотеки остаётся важной даже при записи в файл.
Если смотреть на результаты в целом, наиболее сильные результаты показали logme и spdlog.
Если смотреть на результаты без привязки к конкретному сценарию, можно заметить, что наибольшую производительность показывают logme и spdlog. В разных сценариях лидеры немного меняются, однако в ряде тестов logme показывает заметно более высокий результат.
Интересно выглядит сценарий записи в файл: здесь logme показывает примерно на ~15% более высокую производительность, чем spdlog. При этом в консольных сценариях результаты библиотек оказываются уже значительно ближе друг к другу.
Это довольно интересный результат, поскольку обычно именно запись в файл считается основным ограничением производительности. spdlog и logme.
spdlog показывает очень стабильные результаты во всех сценариях.
logme демонстрирует минимальный overhead вызова логирования и в некоторых тестах значительно опережает другие библиотеки.
Особенно заметна разница в сценарии null, который показывает стоимость самого вызова логирования.
Отдельно стоит отметить, что logme поддерживает несколько вариантов API форматирования (C-style, std::format, iostream). Это позволяет выбирать баланс между удобством и производительностью в зависимости от задачи.
Заключение
Логирование редко рассматривается как часть критического пути выполнения программы. Обычно его воспринимают как вспомогательную инфраструктуру.
Однако в высоконагруженных системах оно может заметно влиять на производительность.
Поэтому иногда имеет смысл провести простой benchmark и посмотреть, сколько реально стоит логирование в вашем приложении.
