mirror of
https://github.com/nlohmann/json.git
synced 2026-10-15 18:57:13 +00:00
Scan the get area of an input stream in bulk
Parsing from a std::istream read one character at a time through the streambuf, so none of the parser's fast paths applied: it was 1.8 to 2.5 times slower than reading the file into a string and parsing that. input_stream_adapter now exposes the streambuf's get area (gptr() to egptr()) to the lexer as a window. The protected get-area members are reached through member pointers formed in a class derived from std::streambuf, which is ordinary, standard-conforming member access. The lexer scans the window with the same bulk paths as a string, and asks the streambuf for the next character as before when the window is used up. Nothing is read ahead, copied, or put back, so the stream is left exactly where it was left before, with and without JSON_PRECISE_STREAM_POSITION, and a streambuf without a get area is still read one character at a time. The adapter also keeps the current token for error messages as a pointer into the get area, and copies it out only before the get area is refilled, instead of the lexer copying every character. On an Apple M1 Max (Apple Clang, -O3), parsing from an std::ifstream is 1.6 to 2.1 times as fast (twitter.json 4.08 -> 2.30 ms, citm_catalog.json 8.75 -> 4.08 ms), and reading 100,000 small values with operator>> 1.4 (objects) to 4.0 (integers) times as fast. With GCC 13 and libstdc++ on x86-64 Linux, std::ifstream is 1.5 to 2.2 times as fast. Parsing from a stream is now within 11 to 18 % of reading the file into a string first. JSON_DISABLE_STREAM_BULK_SCAN restores reading one character at a time, for streambufs that do not follow the usual get-area contract. The new tests compare every observable result (values, exception messages, stream state, remaining characters) for get areas of 1 to 4096 bytes against a streambuf without a get area, and run again with the macro and with JSON_PRECISE_STREAM_POSITION. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
18 files changed
+1177
-20
No files matched your search
@@ -20,6 +20,7 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <sstream>
|
||||
#include <streambuf>
|
||||
@@ -72,6 +73,40 @@ class no_putback_streambuf : public std::streambuf
|
||||
std::size_t m_pos = 0;
|
||||
};
|
||||
|
||||
// A streambuf that serves its input in get areas of a fixed size, so that the
|
||||
// end of a number falls on every possible offset of a get area. The lexer scans
|
||||
// the get area directly; the stream position must not depend on its size.
|
||||
class chunked_streambuf : public std::streambuf
|
||||
{
|
||||
public:
|
||||
chunked_streambuf(std::string s, std::size_t chunk_size)
|
||||
: m_data(std::move(s)), m_chunk_size(chunk_size)
|
||||
{}
|
||||
|
||||
protected:
|
||||
int_type underflow() override
|
||||
{
|
||||
if (gptr() < egptr())
|
||||
{
|
||||
return traits_type::to_int_type(*gptr());
|
||||
}
|
||||
m_pos += static_cast<std::size_t>(egptr() - eback());
|
||||
if (m_pos >= m_data.size())
|
||||
{
|
||||
setg(nullptr, nullptr, nullptr);
|
||||
return traits_type::eof();
|
||||
}
|
||||
char* const begin = &m_data[m_pos];
|
||||
setg(begin, begin, begin + (std::min)(m_chunk_size, m_data.size() - m_pos));
|
||||
return traits_type::to_int_type(*gptr());
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_data;
|
||||
std::size_t m_pos = 0;
|
||||
std::size_t m_chunk_size;
|
||||
};
|
||||
|
||||
// read the characters that are left in a stream
|
||||
std::string remaining(std::istream& is)
|
||||
{
|
||||
@@ -263,4 +298,26 @@ TEST_CASE("JSON_PRECISE_STREAM_POSITION")
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::parse(is), "[json.exception.parse_error.101] parse error: attempting to parse an empty input; check that your input string or stream contains the expected JSON", json::parse_error&);
|
||||
}
|
||||
|
||||
SECTION("the position does not depend on the get area of the streambuf")
|
||||
{
|
||||
const std::vector<std::size_t> chunk_sizes = {1, 2, 3, 4, 5, 7, 8, 13, 4096};
|
||||
for (const std::size_t chunk_size : chunk_sizes)
|
||||
{
|
||||
CAPTURE(chunk_size)
|
||||
chunked_streambuf buf("12345678901234567890true\"a long string value\"-1.25e+3,[1,2]42", chunk_size);
|
||||
std::istream input(&buf);
|
||||
json j;
|
||||
|
||||
input >> j;
|
||||
CHECK(j == 12345678901234567890u);
|
||||
input >> j;
|
||||
CHECK(j == true);
|
||||
input >> j;
|
||||
CHECK(j == "a long string value");
|
||||
input >> j;
|
||||
CHECK(j == -1250.0);
|
||||
CHECK(remaining(input) == ",[1,2]42");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user