XPerf
LanguagesC++

C++ performance guide (v0)

Fewer reallocating containers, quieter hot paths.

Scope: C++17. Speedup claims cite a local bench, an upstream source, or are marked provisional. Corpus residual counts are lint hit counts, not wall-time savings.

cppperf vs cperf: this kit is dedicated C++. Sibling cperf covers C11/C17 (+ light C++). Prefer this package for std::vector / std::string / iostream smells.

Profile first (perf, Tracy, std::chrono). libstdc++ and libc++ differ — re-bench.

Contents

1. Containers and strings

Repeated push_back without reserve reallocates when growth copies dominate (large N / many builders). Prefer sizing capacity once when the final size is known.

std::string += / append of multi-byte chunks or a known final size benefits from reserve. Single-character += under -O2 is often already amortized (~1×) — do not treat every += as a speed bug.

A const std::string or const std::vector parameter, and those types copied in a range-for, copies on every call or element. Out of line, a 64-byte string parameter was about 15× a const reference, and one int in a vector was about 17×. A range-for of 128-byte strings was about 21×, and a range-for of 64-int vectors was about 21×. A 4-character string stayed about 1.01×, an empty vector about 1.2×, and summing every byte of a 4096-byte string about 1.3×. The lint leaves non-const by-value parameters alone, because that is also how a function takes ownership.

Walking the packed bool specialization (vector<bool>) was about 12× a vector<unsigned char> over 2,000,000 elements. Indexed reads were about 4.2× and reserved push_back about 6.9×, under the bar. Eight million elements occupied 1,000,000 bytes in the packed container and 8,000,000 bytes in the byte container. The hint is the walk. It is not a reason to pack other bool fields, and keeping the packed layout is reasonable when the smaller size is the point.

Lint: cx-const-by-value, cx-vector-bool (default hint; measure before escalating). push_back of 500000 ints was about 2.0× without reserve on two runs, and a one-character string::push_back was slower with reserve (about 0.82× and 0.79×). CX001 stays retired: one matcher cannot tell those apart. The 2026-10-22 corpus verdict does not cover cx-const-by-value or cx-vector-bool.

2. Hot I/O

std::cout << ... in a tight loop flushes and formats. Prefer buffered writers or {fmt} / printf outside the loop when profiles show pressure.

No lint rule covers this smell (CX003 was retired).

3. Regex, flush, and the clock

Constructing std::regex inside a loop compiles every iteration. On a short haystack that was about 7.7× versus constructing it once. std::endl on an external stream flushes every insertion (about 23× versus a newline and one flush). The same manipulator on a string stream was about 0.87×, so that shape is not the finding. Sampling steady_clock::now every pass was about 147× versus sampling once; a loop that needs a fresh timestamp has nothing to hoist. Building a string stream every iteration was about 1.2× at -O2 and that rule was removed. These lints stay hint. They are not part of the 2026-10-22 corpus verdict.

ifstream of one path was about 2025× versus opening once. A path that changes every iteration cannot be hoisted. This does not revive the removed CX003 id.

Lint: cx-regex-ctor-hot, cx-endl-hot, cx-clock-hot, cx-ifstream-hot (default hint).

Lint map

IDSmellDefault
CX001removed (jitter)—
cx-regex-ctor-hotstd::regex constructed in loophint (JSONL present; not corpus-triaged)
cx-endl-hotendl flush in loophint (JSONL present; not corpus-triaged)
cx-clock-hotclock sample in a loophint (JSONL; about 147× vs one sample; not corpus-triaged)
cx-ifstream-hotfile stream opened in a loophint (JSONL; about 2025× on one one-byte file; not corpus-triaged)
cx-const-by-valueconst string or vector copied by valuehint (JSONL; about 15–21× when the copy allocates; not corpus-triaged)
cx-vector-boolpacked bool element containerhint (JSONL; walk about 12× vs a byte vector; not corpus-triaged)
CX003removed (jitter)—
CX004removed (jitter)—

Escalate only with evidence/cx-*.jsonl — see evidence-policy.md. The retired CX004 showed that -O2 can erase the effect; measure at -O0 / sanitizers if claiming speed.

References