#series
21 posts tagged #series.
Copying a pmr::vector silently drops its allocator
A pmr::vector looks like a value type, but copy construction does not copy its allocator: select_on_container_copy_construction returns the default resource, so a copied container silently allocates from the global heap. The propagation traits make the allocator sticky. Episode 4 of the pmr series.
Does custom allocation still pay off in 2026?
Custom allocation used to buy 44 percent. A 2026 re-run of the classic study finds modern allocators like mimalloc have closed most of that gap, so a pmr arena now earns its place for predictable latency and fragmentation resilience rather than raw throughput. With a live allocation-count demo. Episode 3 of the pmr series.
A pmr::vector on a stack buffer never calls new
The HFT and game-loop promise of pmr, made testable: override operator new to count heap allocations, then fill a pmr::vector from a monotonic_buffer_resource backed by a stack buffer with null_memory_resource upstream. Ten thousand pushes, zero heap calls. Episode 2 of the pmr series.
std::pmr is one abstract class with three functions
The whole std::pmr framework is one abstract class, std::pmr::memory_resource, with three functions to override: allocate, deallocate, is_equal. A dozen-line logging resource shows a pmr::vector's allocations, then a monotonic_buffer_resource placed in front of it collapses them to zero. Episode 1 of the pmr series.
Validating a string before the program exists
CTRE matches run inside constant expressions, so a regex can check and parse a string literal while the compiler is still working. A malformed version string then stops the build rather than surviving into production, and the parsed result is available as a constant.
The fastest loggers do not format on the calling thread
Every fix in this series synchronizes on the calling thread. The fastest loggers do not: callers enqueue a raw record and a background thread formats and writes it. A mini producer-consumer logger, the spdlog/Quill/NanoLog numbers (around 250 ns versus 7 to 11 ns), and the crash-survivability trade-off. Episode 9 of the concurrent I/O series.
Thread-safe is not reentrant, and the difference bites
The Qt handler contract asked for a reentrant function, not just a thread-safe one, and a std::mutex gives you the second without the first. A demo shows a thread-safe counter and a reentrant recursion that needs a recursive_mutex. The precise taxonomy: reentrant implies thread-safe, never the reverse. Episode 8 of the concurrent I/O series.
The C++20 feature that gave CTRE its interface
Writing ctre::match<"[a-z]+"> requires a string literal to be a template argument, which C++20 allowed and every earlier standard did not. The older spelling still works and still compiles under C++17, so the two sit side by side and show exactly what the feature bought.
The hottest line is often not the one worth optimizing
A sampling profiler tells you where time is spent, which is not the same as where speeding things up would help. Causal profiling answers the second question directly, and it routinely disagrees with the first. A tour of the profilers worth knowing in 2026: Coz, Tracy, poop, samply, and plain perf.
You can see the dependency chain without running anything
Before reaching for a profiler, read what the compiler already produced. Two loops that add the same floats compile to eight adds into one register versus four independent accumulators, and llvm-mca will predict the throughput difference from that assembly without executing a single instruction. Episode 4 of the verification series.
RealtimeSanitizer checks the promise that a function never blocks
Mark a function [[clang::nonblocking]] and RealtimeSanitizer verifies at runtime that nothing inside it allocates, locks, or makes a syscall. It is the sanitizer for audio callbacks, control loops, and any deadline-bound code where a hidden malloc is a dropped frame. Episode 3 of the verification series.
The same assertion, as a unit test and as a fuzzer
Google FuzzTest lets one assertion serve as both a bounded property test that runs with your normal suite and a coverage-guided fuzzer you invoke on demand. Written against a deliberately buggy encoder, it found the counterexample without being asked to fuzz at all, and printed a regression test to paste back.
A real GoogleTest suite runs in your browser
GoogleTest is a Compiler Explorer library, so a real test binary builds and runs in the browser and prints the familiar RUN/OK report. That makes a shareable link the cheapest possible way to settle an argument about behavior. Episode 1 of the verification series, on assembling a C++ setup that catches bugs before your users do.
std::endl is a hidden flush, and clang-tidy flags it
std::endl is not a fancy newline; it is a newline plus a flush, and the flush is what costs you. A flush-counting streambuf proves that a newline flushes zero times and std::endl flushes every time. When to want the flush (crash-survivable logs), when not, and why clang-tidy flags it. Episode 7 of the concurrent I/O series.
Measuring std::regex against a compile-time matcher
std::regex has a reputation, and reputations are worth checking. Measured at its most favourable, with the pattern compiled once outside the timed loop, it takes about seventy times longer per match than CTRE. Include the construction that real code usually pays for and the gap widens by another order of magnitude. Episode 1 of a series on compile-time regular expressions and the language features that make them possible.
Ship the C++20 feature and its fallback in one file
The code review that started this series ended with a portability question: use std::osyncstream where it exists, fall back to a mutex where it does not. The feature-test macro __cpp_lib_syncbuf and <version> are the tool for exactly that. One file, the best available tool on each compiler. Episode 6 of the concurrent I/O series.
C++26 rewrote std::print's internals and backported the fix
std::print's per-call atomicity extends to your own types via std::formatter. How it stays both safe and efficient is a C++26 story: P3107 adds locking-aware entry points and a formatter opt-in to avoid deadlock, and ships as a backport into C++23. Episode 5 of the concurrent I/O series.
std::print does not tear the way std::cout does
The fixes so far needed cooperation or a lock. C++23's std::print gives per-call atomicity for free: a single print call never interleaves with another, the way printf never did and std::cout always could. But the guarantee is per-call, so a line split across two calls can still tear. Episode 4 of the concurrent I/O series.
static std::mutex is safe to construct, thanks to magic statics
Before osyncstream, the fix for a shared log sink was a static std::mutex. But is the static mutex itself safe to initialize under threads? Yes, thanks to C++11 magic statics: a function-local static is constructed exactly once even under a stampede. With the mutex-versus-osyncstream trade-off. Episode 3 of the concurrent I/O series.
std::osyncstream makes concurrent output atomic
std::cout garbled its own output across threads. C++20's std::osyncstream fixes it: each thread buffers a line privately and emits it to the stream atomically on destruction. One wrapper, whole lines, with one sharp edge: the guarantee holds only if every writer uses it. Episode 2 of the concurrent I/O series.
Your std::cout logging has no data race and still tears
Qt's message-handler contract says the handler must be reentrant: called from many threads at once. Point it at std::cout and the output tears, even though the C++ standard guarantees no data race. 'No data race' and 'no interleaving' are different promises, and this series closes the gap. Episode 1 of the concurrent I/O series.