What Is the Performance Impact of spdlog? Benchmarks and Architecture Explained

spdlog adds minimal overhead to C++ applications, achieving up to 14 million messages per second in synchronous null mode and eliminating thread contention via lock-free asynchronous queues.

The gabime/spdlog repository provides a high-performance logging library designed for speed-critical applications. Understanding the performance impact of spdlog helps developers choose between synchronous and asynchronous modes based on their latency and throughput requirements. The library achieves microsecond-level latencies by minimizing formatting costs and using lock-free data structures for inter-thread communication.

Architecture Overview: How spdlog Minimizes Overhead

spdlog reduces logging overhead through three core architectural patterns that separate formatting from I/O and minimize lock contention.

Synchronous Logging

In synchronous mode, each log call writes directly to the sink after minimal formatting. The calling thread blocks until the message is written, making I/O latency the dominant cost. Single-threaded synchronous loggers achieve approximately 5.78 million messages per second with file sinks, while the null_sink (formatting only) reaches 14.13 million messages per second, proving that formatting overhead is negligible compared to disk writes.

Asynchronous Logging with Lock-Free Queues

Asynchronous loggers use a lock-free multiple-producer-multiple-consumer queue (mpmc_blocking_queue) to buffer messages. A background thread pool drains the queue and performs I/O operations. According to the source code in include/spdlog/details/thread_pool.h, this design decouples application threads from I/O latency, reducing the cost of a log call to roughly the time required for formatting and pushing a struct onto the queue.

Overflow Policies for Latency Control

When the async queue fills, spdlog offers three overflow policies configured via async_overflow_policy:

  • block: Waits until space is available, ensuring no message loss but potentially increasing latency
  • overrun_oldest: Discards the oldest message to make room, prioritizing current throughput
  • discard_new: Drops new messages when the queue is full

Benchmark Results: Throughput by Mode

The benchmark suite in bench/bench.cpp demonstrates spdlog's performance characteristics on Ubuntu 64-bit with an i7-4770 processor.

Single-threaded synchronous mode:

  • basic_st: 5.78 million messages/second
  • rotating_st: 5.48 million messages/second
  • daily_st: 5.06 million messages/second
  • null_st: 14.13 million messages/second (formatting only)

Multi-threaded synchronous mode (10 threads):

  • basic_mt: 1.66 million messages/second
  • rotating_mt: 1.61 million messages/second
  • daily_mt: 1.64 million messages/second
  • null_mt: 6.27 million messages/second

Asynchronous mode (10 threads, 8192 queue):

  • Block policy: 0.59 million messages/second
  • Overrun policy: 2.68 million messages/second

These results show that while multi-threaded synchronous logging suffers from mutex contention (dropping to ~1.6M msgs/s), asynchronous logging with the overrun policy achieves 2.68 million messages per second by eliminating producer blocking.

Code Examples: Measuring Impact in Practice

Synchronous Logger Baseline

The basic_logger_mt factory function creates a thread-safe synchronous logger that writes directly to file. As implemented in include/spdlog/spdlog.h, this mode provides the lowest latency for single-threaded applications but encounters lock contention under high concurrency.

#include "spdlog/spdlog.h"

int main() {
    auto logger = spdlog::basic_logger_mt("sync", "logs/sync.log");
    for (int i = 0; i < 1'000'000; ++i) {
        logger->info("Message {}", i);   // ~5M msgs/s single-threaded
    }
}

Asynchronous Logger with Thread Pool

To eliminate I/O blocking, use init_thread_pool and create_async as defined in include/spdlog/async_logger.h. This configuration moves file writes to a background worker thread.

#include "spdlog/async.h"
#include "spdlog/sinks/basic_file_sink.h"

int main() {
    spdlog::init_thread_pool(8192, 1);
    auto async_logger = spdlog::create_async<spdlog::sinks::basic_file_sink_mt>(
        "async", "logs/async.log");

    for (int i = 0; i < 1'000'000; ++i) {
        async_logger->info("Async message {}", i);   // ~2.5M msgs/s multi-threaded
    }
    spdlog::shutdown();
}

Configuring Overflow Policies

Control memory usage and backpressure by specifying the overflow policy during logger creation. The overrun_oldest policy prevents blocking the producer thread when the queue saturates.

spdlog::init_thread_pool(8192, 1);
auto logger = spdlog::create_async<spdlog::sinks::basic_file_sink_mt>(
    "async_overrun",
    "logs/overrun.log",
    spdlog::async_overflow_policy::overrun_oldest);

Key Source Files Affecting Performance

Understanding these implementation files clarifies how spdlog maintains high throughput:

Summary

  • Synchronous mode achieves 5-14 million messages/second depending on I/O requirements, with single-threaded performance exceeding multi-threaded due to lock contention
  • Asynchronous mode eliminates producer blocking via lock-free queues in thread_pool.h, yielding 2.7 million messages/second with the overrun policy
  • Overflow policies allow explicit trade-offs between message durability (block) and throughput (overrun_oldest)
  • Memory overhead is limited to the fixed-size async queue (configurable, typically 8192 entries), making the performance impact predictable and bounded

Frequently Asked Questions

Does spdlog slow down my application?

No, spdlog adds negligible overhead when configured appropriately. In asynchronous mode, the library performs only lock-free queue operations on the calling thread, costing microseconds per call. Even in synchronous mode, throughput exceeds 5 million messages per second on modern hardware, making the impact imperceptible for typical logging volumes.

How fast is spdlog compared to other C++ loggers?

According to the benchmarks in bench/bench.cpp, spdlog ranks among the fastest C++ logging libraries, achieving 14 million messages per second in null-sink mode (formatting only) and 2.7 million messages per second in multi-threaded asynchronous mode. This performance stems from its header-only design, compile-time format string checks, and lock-free queue implementation.

Should I use synchronous or asynchronous logging?

Use synchronous logging for simple single-threaded applications or when immediate disk persistence is required for every message. Use asynchronous logging when logging from multiple threads or when minimizing application latency is critical, as the lock-free queue in include/spdlog/details/thread_pool.h decouples your code from I/O operations.

What is the memory overhead of spdlog's async queue?

The memory overhead is fixed and configurable via init_thread_pool(queue_size, thread_count). Each queue entry stores a formatted log message struct, typically consuming a few hundred bytes per slot. For the default 8192-entry queue used in benchmarks, this translates to approximately a few megabytes of RAM, regardless of message throughput.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →