March 7, 2026

Singletons spark joy? Nah, just wars

Best Performance of a C++ Singleton

Tiny speed tweak, massive comment brawl over singletons

TLDR: The post measures tiny speed differences in a C++ “singleton” pattern, but commenters say the real danger is weird startup order crashes across files. Most back lazy initialization or a one-time setup call for safety, arguing predictable programs beat minuscule speed gains—and that’s what actually saves time.

A new post tries to crown the fastest way to create a “singleton” in C++—that’s a one-per-program global helper—and the author shows two paths: a super-simple version, and a version that adds a tiny safety check for thread-safe startup. In short, the simple one shaves a few instructions; the guarded one plays it safe. You can peek at the details in the author’s breakdown, but the real story? The comments lit up.

The top theme: speed isn’t the real problem. As silverstream puts it, the safety check cost is “a non-issue”; the true villain is the spooky startup order of globals across different files—aka crash gremlins that only appear in release builds. Cue the “just use std::call_once chorus, with signa11 basically saying the whole article could’ve been one line. Others, like m-schuetz, shrug and use a simple struct with static members and an explicit init step. One curious voice, swaminarayan, asks if anyone has ever seen the safety check show up in real performance profiles. The nostalgia cameo: platinumrad returns from a C++ hiatus to remind everyone that lazy initialization—waiting to create the object until it’s first used—sidesteps the startup mess anyway.

Verdict from the peanut gallery: microseconds don’t matter if you’re losing hours to mystery crashes. Safety and predictability beat tiny wins—every time.

Key Points

  • The article evaluates performance trade-offs of a C++ singleton implemented with a function-local static instance.
  • Two constructor styles are compared: a defaultable default constructor versus a user-defined constructor with body.
  • With a defaultable default constructor and trivial initialization, emitted assembly is minimal without guard overhead.
  • A user-defined constructor introduces thread-safe guard checks (e.g., __cxa_guard_acquire) for local static initialization, adding overhead.
  • Copy and move operations, as well as the default constructor, are kept private to enforce the singleton pattern.

Hottest takes

"the guard overhead is a non-issue in practice" — silverstream
"just use `std::call_once` and you are all set." — signa11
"Lazy initialization avoids that problem at very modest cost." — platinumrad
Made with <3 by @siedrix and @shesho from CDMX. Powered by Forge&Hive.