mirror of
https://github.com/nlohmann/json.git
synced 2026-08-20 08:03:18 +00:00
Fill the indentation buffer once instead of once per flush
@gregmarr's point on the fill-and-flush loop: flushing does not disturb what
the write buffer holds, so an indentation spanning several buffer-fulls only
has to be written into the buffer once and can then be handed to the adapter
as many times as needed. The loop re-filled it every time, doing work it
already knew was there.
put_indent() now fills the room left in the buffer, and if anything remains,
flushes, fills the buffer once, and re-flushes that same content. It also
returns early for a zero-width indentation, which is what the closing brace of
every outermost value asks for.
Measured over a dump(), counting memset calls and bytes inside put_indent:
indent before after
4 1 call / 4 B 1 call / 4 B
2000 2 calls / 2000 B 2 calls / 2046 B
100000 98 calls / 100000 B 2 calls / 2046 B
The wide case is now constant work rather than proportional to the indentation
width; ordinary widths are unchanged. Tests extended to cover several whole
buffer-fulls and an exact multiple of the buffer size.
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
@@ -718,24 +718,52 @@ class serializer
|
||||
|
||||
Writes the indentation straight into the buffer instead of copying it out of
|
||||
a pre-grown indentation string, so no auxiliary string has to be sized,
|
||||
resized, or kept in sync with the deepest nesting level reached. An
|
||||
indentation wider than the buffer simply fills and flushes it repeatedly.
|
||||
resized, or kept in sync with the deepest nesting level reached.
|
||||
|
||||
An indentation wider than the buffer is emitted by filling the buffer with
|
||||
the indentation character once and flushing that same content repeatedly:
|
||||
flushing does not disturb what the buffer holds, so re-filling it between
|
||||
flushes would be redundant work.
|
||||
*/
|
||||
void put_indent(unsigned int indent)
|
||||
{
|
||||
while (indent > 0)
|
||||
// closing braces at the outermost level ask for no indentation at all
|
||||
if (indent == 0)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(write_buffer_pos == write_buffer.size()))
|
||||
{
|
||||
flush();
|
||||
}
|
||||
|
||||
const std::size_t chunk = (std::min)(static_cast<std::size_t>(indent),
|
||||
write_buffer.size() - write_buffer_pos);
|
||||
std::memset(write_buffer.data() + write_buffer_pos, indent_char, chunk);
|
||||
write_buffer_pos += chunk;
|
||||
indent -= static_cast<unsigned int>(chunk);
|
||||
return;
|
||||
}
|
||||
|
||||
const std::size_t capacity = write_buffer.size();
|
||||
|
||||
// fill whatever room is left in the buffer; this is the whole job
|
||||
// whenever the indentation is narrower than the buffer, which is the
|
||||
// case for every sane indent_step
|
||||
const std::size_t head = (std::min)(static_cast<std::size_t>(indent), capacity - write_buffer_pos);
|
||||
std::memset(write_buffer.data() + write_buffer_pos, indent_char, head);
|
||||
write_buffer_pos += head;
|
||||
indent -= static_cast<unsigned int>(head);
|
||||
|
||||
if (JSON_HEDLEY_LIKELY(indent == 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// the buffer is full and the remainder spans whole buffer-fulls: flush
|
||||
// what is pending, then fill the buffer with the indentation character
|
||||
// exactly once and hand the same bytes to the adapter as often as needed
|
||||
flush();
|
||||
std::memset(write_buffer.data(), indent_char, capacity);
|
||||
|
||||
while (indent >= capacity)
|
||||
{
|
||||
write_buffer_pos = capacity;
|
||||
flush();
|
||||
indent -= static_cast<unsigned int>(capacity);
|
||||
}
|
||||
|
||||
// the buffer still holds indentation characters throughout, so the tail
|
||||
// only has to be claimed, not written again
|
||||
write_buffer_pos = indent;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
||||
@@ -21599,24 +21599,52 @@ class serializer
|
||||
|
||||
Writes the indentation straight into the buffer instead of copying it out of
|
||||
a pre-grown indentation string, so no auxiliary string has to be sized,
|
||||
resized, or kept in sync with the deepest nesting level reached. An
|
||||
indentation wider than the buffer simply fills and flushes it repeatedly.
|
||||
resized, or kept in sync with the deepest nesting level reached.
|
||||
|
||||
An indentation wider than the buffer is emitted by filling the buffer with
|
||||
the indentation character once and flushing that same content repeatedly:
|
||||
flushing does not disturb what the buffer holds, so re-filling it between
|
||||
flushes would be redundant work.
|
||||
*/
|
||||
void put_indent(unsigned int indent)
|
||||
{
|
||||
while (indent > 0)
|
||||
// closing braces at the outermost level ask for no indentation at all
|
||||
if (indent == 0)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(write_buffer_pos == write_buffer.size()))
|
||||
{
|
||||
flush();
|
||||
}
|
||||
|
||||
const std::size_t chunk = (std::min)(static_cast<std::size_t>(indent),
|
||||
write_buffer.size() - write_buffer_pos);
|
||||
std::memset(write_buffer.data() + write_buffer_pos, indent_char, chunk);
|
||||
write_buffer_pos += chunk;
|
||||
indent -= static_cast<unsigned int>(chunk);
|
||||
return;
|
||||
}
|
||||
|
||||
const std::size_t capacity = write_buffer.size();
|
||||
|
||||
// fill whatever room is left in the buffer; this is the whole job
|
||||
// whenever the indentation is narrower than the buffer, which is the
|
||||
// case for every sane indent_step
|
||||
const std::size_t head = (std::min)(static_cast<std::size_t>(indent), capacity - write_buffer_pos);
|
||||
std::memset(write_buffer.data() + write_buffer_pos, indent_char, head);
|
||||
write_buffer_pos += head;
|
||||
indent -= static_cast<unsigned int>(head);
|
||||
|
||||
if (JSON_HEDLEY_LIKELY(indent == 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// the buffer is full and the remainder spans whole buffer-fulls: flush
|
||||
// what is pending, then fill the buffer with the indentation character
|
||||
// exactly once and hand the same bytes to the adapter as often as needed
|
||||
flush();
|
||||
std::memset(write_buffer.data(), indent_char, capacity);
|
||||
|
||||
while (indent >= capacity)
|
||||
{
|
||||
write_buffer_pos = capacity;
|
||||
flush();
|
||||
indent -= static_cast<unsigned int>(capacity);
|
||||
}
|
||||
|
||||
// the buffer still holds indentation characters throughout, so the tail
|
||||
// only has to be claimed, not written again
|
||||
write_buffer_pos = indent;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
||||
@@ -485,6 +485,12 @@ TEST_CASE("indentation is written straight into the write buffer")
|
||||
// 2000 > the 1024-byte write buffer, and > the 512 the indentation
|
||||
// string used to start at
|
||||
CHECK(j.dump(2000) == "{\n" + std::string(2000, ' ') + "\"a\": 1\n}");
|
||||
// several whole buffer-fulls, so the buffer is refilled once and then
|
||||
// flushed repeatedly
|
||||
CHECK(j.dump(5000) == "{\n" + std::string(5000, ' ') + "\"a\": 1\n}");
|
||||
CHECK(j.dump(5000, '\t') == "{\n" + std::string(5000, '\t') + "\"a\": 1\n}");
|
||||
// an exact multiple of the buffer size
|
||||
CHECK(j.dump(4096) == "{\n" + std::string(4096, ' ') + "\"a\": 1\n}");
|
||||
}
|
||||
|
||||
SECTION("a non-space indentation character is used throughout")
|
||||
|
||||
Reference in New Issue
Block a user