C++ Performance Traps: Hidden Costs of Abstraction and Compiler Optimization Pitfalls
Modern C++ performance suffers when seemingly harmless abstractions—such as virtual functions, hidden copies, non‑trivial destructors, overused std::shared_ptr, type‑erasing wrappers, std::variant/optional, and std::async—prevent inlining and efficient memory use, while patterns like std::move on returns, hidden destructors in tail recursion, and branchy loops thwart NRVO, tail‑call, and auto‑vectorization optimizations.
This article discusses common performance pitfalls in C++ programming, categorized into two main areas: "costly abstractions" and "fighting the compiler."
Part 1: Costly Abstractions
The author challenges the misconception of "Zero Cost Abstraction" in C++, citing Chandler Carruth's statement that "C++ does not have zero-cost abstraction." Key pitfalls include:
Virtual Functions: Cause extra pointer indirection, break CPU pipeline prediction, and most importantly, prevent compiler inlining.
Hidden Copies: Occur in member initialization lists (copy twice), for loops (use const reference), lambda captures (use reference or std::move), and implicit type conversions (use auto&).
Hidden Destructors: Complex types in function scopes can cause significant overhead; defining empty destructors makes classes non-trivially-destructible, preventing register-based returns.
std::shared_ptr Overuse: Construction, copying, and destruction involve atomic operations, 10-20% slower than raw pointers; use std::make_shared for better cache locality.
Type Erasure (std::function, std::any): std::function occupies 32 bytes vs 8 for function pointers, involves virtual calls, and may allocate heap memory.
std::variant and std::optional: Have extra memory overhead and don't work well with NRVO.
std::async: Without explicit policy, may execute synchronously; returned futures block on destruction.
Part 2: Fighting the Compiler
This section covers code patterns that prevent compiler optimization:
NRVO (Named Return Value Optimization): Using std::move on return values prevents optimization and causes extra moves.
Tail Recursion: Hidden destructor calls prevent tail-call optimization; use std::string_view instead of std::string.
Auto-vectorization: Avoid if branches and function calls in loops; use templates and inline functions for SIMD optimization.
Tencent Cloud Developer
Official Tencent Cloud community account that brings together developers, shares practical tech insights, and fosters an influential tech exchange community.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.