mirror of
https://github.com/nlohmann/json.git
synced 2026-10-23 06:18:56 +00:00
Read a streambuf without a get area with a shorter path
A streambuf without a get area - std::cin synchronized with stdio, or a custom unbuffered streambuf - hands out every character from uflow(). #5828 made this path slower than before it (CITM from an unbuffered streambuf: 889M -> 1205M instructions on macOS with JSON_DISABLE_STREAM_BULK_SCAN as the reference), because every character went through the generic refill code of get_character(). Once the adapter has seen that there is no get area, it calls uflow() directly (the get area is known to be exhausted, which is all sbumpc() checks) and copies the character into the token carried by the adapter. The carried token is now a small buffer class that appends with a compare and a store (a std::vector<char>::push_back from this function needed a stack protector canary and a spill on Linux). A get area that appears later is picked up again; a new test serves the first characters without one. Instructions, unbuffered streambuf, relative to the previous commit (develop; JSON_DISABLE_STREAM_BULK_SCAN) on macOS, 5 parses: CITM 1246M -> 960M (1205M; 889M), twitter 556M -> 451M (535M; 410M), canada 2236M -> 1847M (2188M; 1824M); std::cin synchronized with stdio, one parse: CITM 784M -> 727M (776M; 713M), twitter 312M -> 292M (309M; 283M), canada 1142M -> 1067M (1135M; 1062M). Linux Ir (GCC / Clang), one parse: CITM 218.9M / 298.5M -> 185.6M / 219.9M (207.5M / 284.2M; 160.0M / 211.3M), twitter 99.2M / 127.6M -> 87.0M / 98.6M (95.3M / 122.8M; 78.7M / 91.3M), canada 425.7M / 475.1M -> 380.4M / 372.2M (411.7M / 463.4M; 365.9M / 378.8M). Unbuffered input stays slower than with JSON_DISABLE_STREAM_BULK_SCAN: macOS +8 % (CITM) / +10 % (twitter) / +1 % (canada), GCC +16 % / +11 % / +4 %, Clang +4 % / +8 % / -2 % (canada) - every character is still a call plus the virtual uflow(). Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
3 files changed
+306
-12
No files matched your search
@@ -48,14 +48,16 @@ namespace
|
||||
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)
|
||||
// The first @a unbuffered characters are served without a get area, the
|
||||
// rest in get areas of @a chunk_size.
|
||||
chunked_streambuf(std::string s, std::size_t chunk_size, std::size_t unbuffered = 0)
|
||||
: m_data(std::move(s)), m_chunk_size(chunk_size), m_unbuffered(unbuffered)
|
||||
{}
|
||||
|
||||
protected:
|
||||
int_type underflow() override
|
||||
{
|
||||
if (m_chunk_size == 0)
|
||||
if (m_chunk_size == 0 || m_pos < m_unbuffered)
|
||||
{
|
||||
return m_pos < m_data.size() ? traits_type::to_int_type(m_data[m_pos]) : traits_type::eof();
|
||||
}
|
||||
@@ -77,7 +79,7 @@ class chunked_streambuf : public std::streambuf
|
||||
|
||||
int_type uflow() override
|
||||
{
|
||||
if (m_chunk_size == 0)
|
||||
if (m_chunk_size == 0 || m_pos < m_unbuffered)
|
||||
{
|
||||
return m_pos < m_data.size() ? traits_type::to_int_type(m_data[m_pos++]) : traits_type::eof();
|
||||
}
|
||||
@@ -88,6 +90,7 @@ class chunked_streambuf : public std::streambuf
|
||||
std::string m_data;
|
||||
std::size_t m_pos = 0;
|
||||
std::size_t m_chunk_size;
|
||||
std::size_t m_unbuffered;
|
||||
};
|
||||
|
||||
// how a test reads from the stream
|
||||
@@ -101,9 +104,9 @@ enum class read_mode
|
||||
// Read @a input from a chunked_streambuf and describe everything observable:
|
||||
// the values or the exception, the stream state after each value, and the
|
||||
// characters left in the stream.
|
||||
std::string observe(const std::string& input, std::size_t chunk_size, read_mode mode)
|
||||
std::string observe(const std::string& input, std::size_t chunk_size, read_mode mode, std::size_t unbuffered = 0)
|
||||
{
|
||||
chunked_streambuf buf(input, chunk_size);
|
||||
chunked_streambuf buf(input, chunk_size, unbuffered);
|
||||
std::istream is(&buf);
|
||||
std::string result;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
@@ -212,6 +215,31 @@ TEST_CASE("parsing from a std::istream scans the get area in bulk")
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("results do not depend on a get area that appears late")
|
||||
{
|
||||
// the first characters come without a get area, then the streambuf
|
||||
// starts to use one: the token that is read at that point is complete
|
||||
for (const auto& input : corpus())
|
||||
{
|
||||
const std::string expected = observe(input, 0, read_mode::extract);
|
||||
for (const std::size_t unbuffered :
|
||||
{
|
||||
1u, 2u, 3u, 5u, 8u, 13u, 21u
|
||||
})
|
||||
{
|
||||
for (const std::size_t chunk_size :
|
||||
{
|
||||
1u, 2u, 3u, 7u, 64u
|
||||
})
|
||||
{
|
||||
CAPTURE(input)
|
||||
CAPTURE(unbuffered)
|
||||
CAPTURE(chunk_size)
|
||||
CHECK(observe(input, chunk_size, read_mode::extract, unbuffered) == expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // !defined(JSON_NOEXCEPTION)
|
||||
|
||||
SECTION("values are the same as when parsing from a string")
|
||||
|
||||
Reference in new issue
Block a user