From 419133b2af243cf7bee21ba2db2f78c5a2619146 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Wed, 21 Oct 2026 21:32:52 +0200 Subject: [PATCH] Step over a character in the get area without leaving get_character() input_stream_adapter::get_character() was an out-of-line function that checked for a refill, called sbumpc(), checked for EOF and updated the token bookkeeping for every character. Characters that come from the get area - nearly all of them - now take a short inline path (pointer compare, one store, gbump(1)); the refill, EOF and no-get-area cases moved to an out-of-line get_character_slow(). Default mode only; with JSON_PRECISE_STREAM_POSITION the adapter is unchanged. Instructions, istream/istringstream/custom streambuf, relative to develop (macOS, 5 parses): CITM from an ifstream 399M -> 380M (-5 %), from an istringstream 383M -> 366M, twitter 186M -> 179M, canada 979M -> 953M, accept() twitter 109M -> 102M, accept() CITM 250M -> 231M. Linux Ir (GCC / Clang): CITM 89.0M / 122.1M -> 84.6M / 118.7M, twitter 42.5M / 54.3M -> 40.8M / 52.9M, canada 198.3M / 243.3M -> 192.0M / 237.4M, accept() twitter 25.4M / 36.9M -> 23.7M / 35.5M, NDJSON 95.5M / 118.7M -> 91.2M / 115.2M. FILE* input is unchanged. Streambufs without a get area get 3-5 % slower with this commit alone (CITM 1205M -> 1246M on macOS, 207.5M -> 218.9M Ir with GCC); a later commit in this series makes that path shorter than before. Signed-off-by: Niels Lohmann --- .../nlohmann/detail/input/input_adapters.hpp | 74 ++++++++++++------- single_include/nlohmann/json.hpp | 74 ++++++++++++------- 2 files changed, 94 insertions(+), 54 deletions(-) diff --git a/include/nlohmann/detail/input/input_adapters.hpp b/include/nlohmann/detail/input/input_adapters.hpp index 77a1b45c5..f9a79e42d 100644 --- a/include/nlohmann/detail/input/input_adapters.hpp +++ b/include/nlohmann/detail/input/input_adapters.hpp @@ -295,37 +295,16 @@ class input_stream_adapter std::char_traits::int_type get_character() { #if !JSON_DISABLE_STREAM_BULK_SCAN - // sbumpc() refills the get area if it is exhausted - const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb); - if (JSON_HEDLEY_UNLIKELY(refill)) - { - save_token_segment(); - } -#endif - - auto res = sb->sbumpc(); - // set eof manually, as we don't use the istream interface. - if (JSON_HEDLEY_UNLIKELY(res == std::char_traits::eof())) - { -#if !JSON_DISABLE_STREAM_BULK_SCAN - current_in_get_area = false; -#endif - is->clear(is->rdstate() | std::ios::eofbit); - } -#if !JSON_DISABLE_STREAM_BULK_SCAN - else if (JSON_HEDLEY_UNLIKELY(refill)) - { - // the consumed character is at gptr() - 1, unless the streambuf - // has no get area and returned it from uflow() - const char* const next = streambuf_access::get_ptr(sb); - after_refill(res, next != nullptr && next != streambuf_access::get_begin(sb)); - } - else + // the character is in the get area: step over it, as sbumpc() does + const char* const next = streambuf_access::get_ptr(sb); + if (JSON_HEDLEY_LIKELY(next != streambuf_access::get_end(sb))) { current_in_get_area = true; + streambuf_access::bump(sb, 1); + return std::char_traits::to_int_type(*next); } #endif - return res; + return get_character_slow(); } #endif @@ -434,6 +413,47 @@ class input_stream_adapter #endif private: +#if !JSON_PRECISE_STREAM_POSITION + // the get area is exhausted, or there is none +#if !JSON_DISABLE_STREAM_BULK_SCAN + JSON_HEDLEY_NEVER_INLINE +#endif + std::char_traits::int_type get_character_slow() + { +#if !JSON_DISABLE_STREAM_BULK_SCAN + // sbumpc() refills the get area if it is exhausted + const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb); + if (JSON_HEDLEY_UNLIKELY(refill)) + { + save_token_segment(); + } +#endif + + auto res = sb->sbumpc(); + // set eof manually, as we don't use the istream interface. + if (JSON_HEDLEY_UNLIKELY(res == std::char_traits::eof())) + { +#if !JSON_DISABLE_STREAM_BULK_SCAN + current_in_get_area = false; +#endif + is->clear(is->rdstate() | std::ios::eofbit); + } +#if !JSON_DISABLE_STREAM_BULK_SCAN + else if (JSON_HEDLEY_UNLIKELY(refill)) + { + // the consumed character is at gptr() - 1, unless the streambuf + // has no get area and returned it from uflow() + const char* const next = streambuf_access::get_ptr(sb); + after_refill(res, next != nullptr && next != streambuf_access::get_begin(sb)); + } + else + { + current_in_get_area = true; + } +#endif + return res; + } +#endif #if !JSON_DISABLE_STREAM_BULK_SCAN // Reads the protected get-area pointers of any std::streambuf. Naming the // members through a derived class makes forming the member pointers diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 89e89c12a..4c466ee98 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -8760,37 +8760,16 @@ class input_stream_adapter std::char_traits::int_type get_character() { #if !JSON_DISABLE_STREAM_BULK_SCAN - // sbumpc() refills the get area if it is exhausted - const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb); - if (JSON_HEDLEY_UNLIKELY(refill)) - { - save_token_segment(); - } -#endif - - auto res = sb->sbumpc(); - // set eof manually, as we don't use the istream interface. - if (JSON_HEDLEY_UNLIKELY(res == std::char_traits::eof())) - { -#if !JSON_DISABLE_STREAM_BULK_SCAN - current_in_get_area = false; -#endif - is->clear(is->rdstate() | std::ios::eofbit); - } -#if !JSON_DISABLE_STREAM_BULK_SCAN - else if (JSON_HEDLEY_UNLIKELY(refill)) - { - // the consumed character is at gptr() - 1, unless the streambuf - // has no get area and returned it from uflow() - const char* const next = streambuf_access::get_ptr(sb); - after_refill(res, next != nullptr && next != streambuf_access::get_begin(sb)); - } - else + // the character is in the get area: step over it, as sbumpc() does + const char* const next = streambuf_access::get_ptr(sb); + if (JSON_HEDLEY_LIKELY(next != streambuf_access::get_end(sb))) { current_in_get_area = true; + streambuf_access::bump(sb, 1); + return std::char_traits::to_int_type(*next); } #endif - return res; + return get_character_slow(); } #endif @@ -8899,6 +8878,47 @@ class input_stream_adapter #endif private: +#if !JSON_PRECISE_STREAM_POSITION + // the get area is exhausted, or there is none +#if !JSON_DISABLE_STREAM_BULK_SCAN + JSON_HEDLEY_NEVER_INLINE +#endif + std::char_traits::int_type get_character_slow() + { +#if !JSON_DISABLE_STREAM_BULK_SCAN + // sbumpc() refills the get area if it is exhausted + const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb); + if (JSON_HEDLEY_UNLIKELY(refill)) + { + save_token_segment(); + } +#endif + + auto res = sb->sbumpc(); + // set eof manually, as we don't use the istream interface. + if (JSON_HEDLEY_UNLIKELY(res == std::char_traits::eof())) + { +#if !JSON_DISABLE_STREAM_BULK_SCAN + current_in_get_area = false; +#endif + is->clear(is->rdstate() | std::ios::eofbit); + } +#if !JSON_DISABLE_STREAM_BULK_SCAN + else if (JSON_HEDLEY_UNLIKELY(refill)) + { + // the consumed character is at gptr() - 1, unless the streambuf + // has no get area and returned it from uflow() + const char* const next = streambuf_access::get_ptr(sb); + after_refill(res, next != nullptr && next != streambuf_access::get_begin(sb)); + } + else + { + current_in_get_area = true; + } +#endif + return res; + } +#endif #if !JSON_DISABLE_STREAM_BULK_SCAN // Reads the protected get-area pointers of any std::streambuf. Naming the // members through a derived class makes forming the member pointers