Skip to content

Find the stop byte of a string run without a byte loop - #5618

Closed
nlohmann wants to merge 5 commits into
json-view/02b-float-parserfrom
json-view/03-string-scan
Closed

nlohmann wants to merge 5 commits into
json-view/02b-float-parserfrom
json-view/03-string-scan

Conversation

@nlohmann

@nlohmann nlohmann commented Sep 29, 2026 •

Copy link
Copy Markdown
Owner

Part of the stack for the zero-copy view (#5295). This change speeds up json::parse and dump() on its own; the view uses the same kernels later.

Summary

The string kernels in detail/input/string_scan.hpp test eight bytes at a time. Until now, once a word contained a byte to stop at, the kernels found that byte with a byte loop. The lowest flagged byte of the SWAR tests is always a real hit, because the borrows of the subtractions can only flag bytes above one. So its index is now the trailing-zero count of the mask.

Words are assembled in little-endian order on every platform, so the result doesn't depend on the byte order. count_trailing_zeros() has a portable fallback.

Non-ASCII text: scalar_string_bulk_run() now validates a run of multi-byte UTF-8 sequences one after another, instead of searching for the next special byte after each sequence. This helps text in non-Latin scripts.

The kernels serve the lexer's contiguous fast path, the serializer, and the binary formats.

Tests

  • New tests compare all three kernels with byte-by-byte reference scans on 100,000 generated buffers, at three alignments.
  • The portable fallback of count_trailing_zeros() is checked against the builtin.
  • The existing suite passes under AddressSanitizer and UndefinedBehaviorSanitizer.

Benchmarks

tests/benchmarks: Apple M1 Max, Apple clang 21, -O3 -flto, median of 2×5 interleaved repetitions:

benchmark change
ParseString/twitter −7.1%
ParseString/jeopardy −4.2%
ParseIndented/twitter −8.3%
Dump/twitter −16%
Dump/jeopardy −7.2%
Dump/citm_catalog −4.7%
other ParseString/Dump rows within ±2%

ParseFile reads from a stream and does not use these kernels; its rows are unchanged (within noise).

json::parse in a separate process, best of 5:

file change
poet.json (Chinese text) −27%
random.json −7.6%
gsoc-2018.json −6.2%
twitter.json −5.6%
each of the other 10 corpus files 0.4–4.6% faster

Public API

No change.


Written by Claude Code.

🤖 Generated with Claude Code

@nlohmann
nlohmann added this pull request to stack #5636 September 29, 2026 14:20
@nlohmann

Copy link
Copy Markdown
Owner Author

CI fixes for this PR (commit 046ad7e, plus the fixes from #5617 merged in):

  • ci_test_gcc, ci_test_standards_gcc (11): -Werror=useless-cast on static_cast<std::size_t>(next() % n) in the string-scan test of unit-class_lexer.cpp. On 64-bit Linux, std::uint64_t and std::size_t are the same type, while the cast is needed where std::size_t is 32 bits. The sizes now come from a 32-bit value (next_small()), which converts to std::size_t implicitly on every platform.
  • ci_clang_tidy: inherited from Convert floats with Eisel-Lemire when std::from_chars is unavailable #5617 and fixed there.

Verified with GCC 16 using the CI flags (the test passes).

#5619 (json-view/04-unicode-escapes) got only a propagation merge; its failures were all inherited from here and from #5617.

— posted by Claude Code on behalf of @nlohmann

@nlohmann
nlohmann removed this pull request from stack #5636 September 30, 2026 13:19
@nlohmann
nlohmann force-pushed the json-view/03-string-scan branch from 046ad7e to 54c0c07 Compare September 30, 2026 13:20
@nlohmann
nlohmann changed the base branch from json-view/02-eisel-lemire to json-view/02b-float-parser September 30, 2026 13:21
@nlohmann
nlohmann added this pull request to stack #5739 September 30, 2026 13:21
@nlohmann
nlohmann force-pushed the json-view/03-string-scan branch 3 times, most recently from bcbee74 to 1df6f5e Compare September 30, 2026 18:06
@nlohmann
nlohmann marked this pull request as ready for review September 30, 2026 18:18
find_string_special() and find_ascii_copyable_run() test eight bytes at a
time, but located the stopping byte inside a word with a byte loop. The
lowest flagged byte of the SWAR tests is always a true hit (the borrows of
the subtractions can only flag bytes above one), so its index is now the
trailing-zero count of the mask; words are read in little-endian order on
every platform, so this does not depend on the byte order.
scalar_string_bulk_run() validates a run of multi-byte UTF-8 sequences one
after another instead of searching for the next special byte in between,
which helps text in non-Latin scripts.

The kernels serve the lexer's contiguous fast path, the serializer, and the
binary formats. New tests compare all three with byte-by-byte reference
scans on 100,000 generated buffers at three alignments; the portable
fallback of count_trailing_zeros() was checked against the builtin.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
GCC -Werror=useless-cast rejected static_cast<std::size_t>(next() % n):
on 64-bit Linux std::uint64_t and std::size_t are the same type, while
the cast is needed where std::size_t is 32 bits wide. Draw the sizes from
a 32-bit value instead, which converts to std::size_t implicitly on every
platform.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
@nlohmann
nlohmann force-pushed the json-view/03-string-scan branch from 1df6f5e to f1014c9 Compare September 30, 2026 18:19
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
… HEAD

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
@nlohmann
nlohmann removed this pull request from stack #5739 October 6, 2026 09:26
@nlohmann

nlohmann commented Oct 6, 2026

Copy link
Copy Markdown
Owner Author

The zero-copy view stack (#5295) was rebased onto develop and regrouped from 20 PRs into 10, one squashed commit each. The changes of this PR are now in #5738; its review comments stay here for reference.


Written by Claude Code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant