Skip to content

Update Sanitizer - #145

Merged
ecbadeaux merged 1 commit into
mainfrom
fast-sanitizer
Oct 19, 2026
Merged

ecbadeaux merged 1 commit into
mainfrom
fast-sanitizer

Conversation

@ecbadeaux

Copy link
Copy Markdown
Contributor

Replace regex tag sanitizer with a table-driven one

Summary

MeterId built its spectatord id by running std::regex_replace on the name and on every tag key and value, then joining them with an ostringstream. This was the dominant cost of r.CreateCounter(name, tags). This PR replaces it with a compile-time lookup table and a single pre-sized string.

  • AppendSanitized writes each byte as kValidChars[byte] ? byte : '_'. The 256-entry table is built by a constexpr function, so nothing runs at startup. The old global std::regex is gone.
  • ToSpectatorId computes the exact output size, reserves once, and appends with no ostringstream.
  • Helpers are in an anonymous namespace, so there is no new public API and no version bump is needed. meter_id.h still includes <regex> so code that relied on it keeps compiling.
  • Emitted lines are byte-for-byte unchanged.

Why this is faster than the per-character version

Google Benchmark on an id with a 35-character name and 7 tags:

Variant Time
Original (regex + ostringstream) 10,905 ns
Pre-sized string, per-character += with range checks 370 ns
This PR: resize once + lookup table 130 ns

Both new versions reserve the output up front, so neither reallocates. The difference is what happens per character.

  • Per-character +=: reserving does not change the size, so every append still increases the string's size by one. Each iteration writes the new size back, stores the byte, rewrites the trailing NUL, and re-checks short-string vs heap mode. The generated code makes an out-of-line push_back call per character for the name.
  • This PR: the string is resized to its final length before the loop, since sanitizing never changes length. The loop only reads a byte, looks it up, and writes a byte through a raw pointer. It never touches the size field, and the compiler unrolls it.
  • Range checks vs table: the table replaces about eight comparisons per byte with one load.

@ecbadeaux
ecbadeaux force-pushed the fast-sanitizer branch 3 times, most recently from d1d3931 to c553252 Compare October 19, 2026 05:35
@ecbadeaux
ecbadeaux merged commit 5f55087 into main Oct 19, 2026
1 check passed
@ecbadeaux
ecbadeaux deleted the fast-sanitizer branch October 19, 2026 06:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant