-
Notifications
You must be signed in to change notification settings - Fork 3
Performance
Performance is one of the main design goals of logme. The important point is that logme is not fast only when a message is actually written. It is also optimized for the common production case where detailed logging exists in the source code but is disabled, filtered out, or routed only when diagnostics are required.
The performance results below are taken from the separate logbench repository. That repository contains the benchmark source code, build scripts, an article, and charts, so the numbers are reproducible rather than being hand-written claims in this wiki.
The latest table below shows the number of completed benchmark cycles during the configured benchmark interval. Higher is better.
| Library | null | file | console | file+console |
|---|---|---|---|---|
| logme (c) | 1,121,998,419 | 153,752,170 | 325,518 | 359,772 |
| logme (cpp-stream) | 1,053,074,266 | 24,290,646 | 335,276 | 364,349 |
| logme (std::format) | 1,082,512,863 | 90,255,191 | 362,369 | 365,770 |
| spdlog | 244,991,156 | 118,546,708 | 368,944 | 355,449 |
| quill | 1,568,610 | 1,357,532 | 191,531 | 200,378 |
| easylogging++ | 25,597,177 | 1,656,726 | 356,948 | 277,846 |
These results are intentionally shown together with the benchmark repository link. Extremely large differences are easy to misread if the benchmark setup is not visible. For methodology, command lines, charts, and the full discussion, see the logbench article: docs/article.md.
The null column measures the hot path where logging calls exist but no output is produced. This is a critical production scenario: detailed diagnostic points can remain in the code, but the application should not pay formatting and I/O costs when those messages are not needed.
In this mode, logme's C-style, stream-style, and std::format-style APIs are all above one billion completed cycles in the benchmark interval. The result reflects the architecture: early checks, cheap filtering, and per-call-site cached context are used to avoid repeated setup work.
The file column measures output to file. logme's C-style API is especially strong here because it combines a low-cost formatting path with asynchronous file output. Stream-style logging is still fast in the disabled/null case, but it is naturally more expensive when actual message construction is required.
The console and file+console columns are much closer across libraries because console output itself becomes the limiting factor. logme still remains competitive while preserving ordering and routing behavior.
logme's performance is not based on a single benchmark shortcut. Several mechanisms work together.
When _LOGME_ACTIVE is false, the macros compile to an inert branch. No logging work is performed at runtime because the call site is effectively removed.
For calls that provide an explicit ChannelPtr, and for Do macros that can resolve the channel before preparation code runs, Detail/Precheck.h can reject the call before expensive user expressions are evaluated.
This does not mean that every disabled log call is magically free. It means that hot paths can be written so that filtering happens before formatting and before expensive argument preparation.
Macros use a static ContextCache per call site. This reduces repeated setup work for metadata, formatting state, and context preparation. A logging call that is executed many times does not have to rediscover the same static information on every iteration.
logme supports C-style formatting, C++ stream-style output, and optional {} formatting through the configured format backend. These modes have different costs, and the benchmark reports them separately instead of hiding the difference behind one generic number.
FileBackend batches data through BufferQueue and a shared file-manager thread. This avoids forcing every producer thread to synchronously perform physical file I/O for every line.
The channel/subsystem/backend model allows expensive diagnostics to exist in the code but remain inactive until a specific channel or subsystem is enabled. This is one of logme's core production use cases: detailed logs are available on demand without permanently increasing output volume.
A CPU or I/O profiler can show that time is spent in FileBackend, queue synchronization, formatting, or operating-system write calls. It normally cannot identify which source statements caused that work.
The on-demand logstat profiler fills that gap:
logmectl -p 7791 logstat start
# Reproduce the workload.
logmectl -p 7791 logstat stop
logmectl -p 7791 logstat outputs --backend FileBackend --sort bytes --limit 30
logmectl -p 7791 logstat outputs --backend FileBackend --sort records --limit 30This separates two different performance problems:
- a small number of large records or payload dumps;
- a very large number of small records that spend CPU on formatting, queueing, synchronization, and worker wakeups.
Collection is disabled by default. While disabled, the profiler performs no call-site registration, counter updates, locking, allocation, time queries, or metadata copying. For an already accepted record, the source-site and built-in backend paths add a relaxed atomic pointer load and a normally predicted null branch. Calls rejected by the existing early filters do not reach those checks.
See Log Source Profiling for backend fan-out, asynchronous file-worker statistics, result interpretation, and the full idle-versus-load workflow.
The following features are intentionally optional because they have a cost:
-
std::formatformatting - C++ stream message construction when output is actually produced
- per-message overrides and rate limiting
- display filters and global condition callbacks
- subsystem checks
- obfuscation/encryption for file output
- console highlighting and extra output fields
For hot paths:
- keep expensive argument computation inside logging macros or
Doforms - prefer explicit
ChannelPtrlogging when you want early precheck to help - use C-style logging for the lowest active formatting overhead
- use stream-style logging when readability and incremental construction matter more than raw active-output throughput
- disable unnecessary output fields
- use
FileBackendwhen batching matters - call
Flush()only when you truly need a synchronization point
logme is not presented as:
- fully lock-free
- allocation-free in all formatting modes
- async-signal-safe
- faster than the console or the operating system's I/O path
The benchmark should be read as a reproducible measurement of the tested scenarios, not as a universal promise for every application. The important design goal is more practical: keep detailed logging cheap enough that it can remain available in production and be enabled only when needed.
logme — flexible runtime logging system
Home · Getting Started · How-To · Runbooks · Architecture · Output · Backends · Configuration
GitHub: https://github.com/efmsoft/logme
- Home
- How-To Guide
- Getting Started
- Why logme?
- Core Concepts
- Logging Macros
- Fatal Handling
- Crash Logging
- glog Compatibility
- C API
- Choosing Logging Macros
- Function tracing
- Trace Points
- Override Scopes
- Advanced Features
- Collapse Logging
- Feature Map
- Production File Logging
- Readable Logging Topology
- Live Diagnostics
- Logging Performance Investigation
- Startup, Fatal, and Crash Diagnostics
- Troubleshooting Missing or Duplicate Logs
- Application and Platform Integration
- Structured and Protected Logs
- Migration to logme
- Overview
- Console Backend
- Debugger Backend
- File Backend
- File Rotation & Retention
- Buffer Backend
- Ring Buffer Backend
- SharedFile Backend
- Callback Backend
- Windows Event Log Backend
- Custom Backends
- Runtime Control
- Configuration
- Configuration JSON
- Control Server
- Environment Control
- Control Policies
- Trace Points
- Log Source Profiling
- Message Filtering