Fix codegen regression in skip_whitespace() from #5490

Benchmarking found the get()/get_ignoring_pending_unget() split in
skip_whitespace() made long whitespace runs (e.g. indentation in
pretty-printed JSON) 1.75x-3.2x SLOWER instead of faster, reproducible
with both Apple Clang and GCC.

Root cause: rewriting the loop from a plain do-while into an initial
get() followed by a while-loop defeated the compiler's ability to keep
the input adapter's read/end pointers in registers across iterations;
both compilers instead reloaded them from memory on every character.
The function split itself was not the problem (it still fully
inlines); the loop's control-flow shape was.

The fix keeps the same two-function structure but restores a
do-while shape (guarded by an if for the "first char not whitespace"
case), which lets both compilers hoist the pointers back into
registers, matching or beating pre-#5490 performance.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann
2026-09-05 22:19:36 +02:00
committed by GitHub
parent 8017d6b66a
commit 3eb414f832
2 changed files with 26 additions and 4 deletions
+13 -2
View File
@@ -1705,9 +1705,20 @@ scan_number_done:
// nothing below calls unget()
get();
while (current == ' ' || current == '\t' || current == '\n' || current == '\r')
// this is written as an if-guarded do-while (rather than a plain
// while loop) because that shape is what lets both GCC and Clang
// keep the input adapter's read pointer in a register across
// iterations; the equivalent while-loop measurably defeated that
// optimization in testing, turning long whitespace runs (e.g. the
// indentation of pretty-printed JSON) from a register-only loop
// into one that reloads the pointer from memory every character
if (current == ' ' || current == '\t' || current == '\n' || current == '\r')
{
get_ignoring_pending_unget();
do
{
get_ignoring_pending_unget();
}
while (current == ' ' || current == '\t' || current == '\n' || current == '\r');
}
}