mirror of
https://github.com/nlohmann/json.git
synced 2026-08-22 00:53: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
|
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,
|
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
|
resized, or kept in sync with the deepest nesting level reached.
|
||||||
indentation wider than the buffer simply fills and flushes it repeatedly.
|
|
||||||
|
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)
|
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()))
|
return;
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|||||||
@@ -21651,24 +21651,52 @@ class serializer
|
|||||||
|
|
||||||
Writes the indentation straight into the buffer instead of copying it out of
|
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,
|
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
|
resized, or kept in sync with the deepest nesting level reached.
|
||||||
indentation wider than the buffer simply fills and flushes it repeatedly.
|
|
||||||
|
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)
|
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()))
|
return;
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// 2000 > the 1024-byte write buffer, and > the 512 the indentation
|
||||||
// string used to start at
|
// string used to start at
|
||||||
CHECK(j.dump(2000) == "{\n" + std::string(2000, ' ') + "\"a\": 1\n}");
|
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")
|
SECTION("a non-space indentation character is used throughout")
|
||||||
|
|||||||
Reference in New Issue
Block a user