From 47462527b23dfaa279e01271635de23fb3a7c211 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 16 Oct 2026 22:11:08 +0200 Subject: [PATCH] Write a line break and the indentation of the next line together A pretty-printed array or object wrote ",\n", checked the room for the indentation, and stored it, for every element. The line break and the usual indentation (up to 32 characters, with room in the write buffer) are now written with one check and one run of stores, also after the opening bracket and in front of the closing one. Other indentations take the unchanged path. Apple M1, Apple clang, retired instructions per dump(4), on top of the previous changes: citm_catalog 7.28M -> 7.06M (-3 %) canada 44.4M -> 43.3M (-3 %) twitter 3.91M -> 3.78M (-3 %) jeopardy -1 % Linux x86-64 (callgrind), gcc 13 / clang 18, dump(4) / dump(1, '\t'): citm_catalog dump(4) -3 % / -4 %, tab -4 % / -5 %, with the function inlined (without that GCC wrote the combined step out of line and was 5 % slower than before the change). Compact output is unchanged. Signed-off-by: Niels Lohmann --- include/nlohmann/detail/output/serializer.hpp | 48 +++++++++++++------ single_include/nlohmann/json.hpp | 48 +++++++++++++------ 2 files changed, 68 insertions(+), 28 deletions(-) diff --git a/include/nlohmann/detail/output/serializer.hpp b/include/nlohmann/detail/output/serializer.hpp index cbde53cc4..640f9920e 100644 --- a/include/nlohmann/detail/output/serializer.hpp +++ b/include/nlohmann/detail/output/serializer.hpp @@ -167,30 +167,27 @@ class serializer if (pretty_print) { - put_literal("{\n"); - // variable to hold indentation for recursive calls const auto new_indent = next_indent(current_indent, indent_step); + put_literal_indent("{\n", new_indent); + // first n-1 elements auto i = val.m_data.m_value.object->cbegin(); for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i) { - put_indent(new_indent); put_quoted(i->first, "\": "); dump_element(i->second, new_indent, depth + 1); - put_literal(",\n"); + put_literal_indent(",\n", new_indent); } // last element JSON_ASSERT(i != val.m_data.m_value.object->cend()); JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend()); - put_indent(new_indent); put_quoted(i->first, "\": "); dump_element(i->second, new_indent, depth + 1); - put_char('\n'); - put_indent(current_indent); + put_literal_indent("\n", current_indent); put_char('}'); } else @@ -234,27 +231,24 @@ class serializer if (pretty_print) { - put_literal("[\n"); - // variable to hold indentation for recursive calls const auto new_indent = next_indent(current_indent, indent_step); + put_literal_indent("[\n", new_indent); + // first n-1 elements for (auto i = val.m_data.m_value.array->cbegin(); i != val.m_data.m_value.array->cend() - 1; ++i) { - put_indent(new_indent); dump_element(*i, new_indent, depth + 1); - put_literal(",\n"); + put_literal_indent(",\n", new_indent); } // last element JSON_ASSERT(!val.m_data.m_value.array->empty()); - put_indent(new_indent); dump_element(val.m_data.m_value.array->back(), new_indent, depth + 1); - put_char('\n'); - put_indent(current_indent); + put_literal_indent("\n", current_indent); put_char(']'); } else @@ -1430,6 +1424,32 @@ class serializer write_buffer_pos = indent; } + /*! + @brief append a string literal and then @a indent indentation characters + + The usual line break and indentation go into the buffer with one check of + its room and four stores of the indentation, see @ref put_indent. + */ + template + JSON_HEDLEY_ALWAYS_INLINE void put_literal_indent(const char (&s)[N], const std::size_t indent) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + { + constexpr std::size_t length = N - 1; + if (JSON_HEDLEY_LIKELY(indent <= 32 && write_buffer_pos + length + 32 <= write_buffer.size())) + { + const std::uint64_t word = 0x0101010101010101ull * static_cast(indent_char); + char* const d = write_buffer.data() + write_buffer_pos; + std::memcpy(d, s, length); + std::memcpy(d + length, &word, sizeof(word)); + std::memcpy(d + length + 8, &word, sizeof(word)); + std::memcpy(d + length + 16, &word, sizeof(word)); + std::memcpy(d + length + 24, &word, sizeof(word)); + write_buffer_pos += length + indent; + return; + } + put_literal(s); + put_indent(indent); + } + /*! @brief append a string literal to the write buffer diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index e37ec2e60..88d2df2be 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -30311,30 +30311,27 @@ class serializer if (pretty_print) { - put_literal("{\n"); - // variable to hold indentation for recursive calls const auto new_indent = next_indent(current_indent, indent_step); + put_literal_indent("{\n", new_indent); + // first n-1 elements auto i = val.m_data.m_value.object->cbegin(); for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i) { - put_indent(new_indent); put_quoted(i->first, "\": "); dump_element(i->second, new_indent, depth + 1); - put_literal(",\n"); + put_literal_indent(",\n", new_indent); } // last element JSON_ASSERT(i != val.m_data.m_value.object->cend()); JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend()); - put_indent(new_indent); put_quoted(i->first, "\": "); dump_element(i->second, new_indent, depth + 1); - put_char('\n'); - put_indent(current_indent); + put_literal_indent("\n", current_indent); put_char('}'); } else @@ -30378,27 +30375,24 @@ class serializer if (pretty_print) { - put_literal("[\n"); - // variable to hold indentation for recursive calls const auto new_indent = next_indent(current_indent, indent_step); + put_literal_indent("[\n", new_indent); + // first n-1 elements for (auto i = val.m_data.m_value.array->cbegin(); i != val.m_data.m_value.array->cend() - 1; ++i) { - put_indent(new_indent); dump_element(*i, new_indent, depth + 1); - put_literal(",\n"); + put_literal_indent(",\n", new_indent); } // last element JSON_ASSERT(!val.m_data.m_value.array->empty()); - put_indent(new_indent); dump_element(val.m_data.m_value.array->back(), new_indent, depth + 1); - put_char('\n'); - put_indent(current_indent); + put_literal_indent("\n", current_indent); put_char(']'); } else @@ -31574,6 +31568,32 @@ class serializer write_buffer_pos = indent; } + /*! + @brief append a string literal and then @a indent indentation characters + + The usual line break and indentation go into the buffer with one check of + its room and four stores of the indentation, see @ref put_indent. + */ + template + JSON_HEDLEY_ALWAYS_INLINE void put_literal_indent(const char (&s)[N], const std::size_t indent) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) + { + constexpr std::size_t length = N - 1; + if (JSON_HEDLEY_LIKELY(indent <= 32 && write_buffer_pos + length + 32 <= write_buffer.size())) + { + const std::uint64_t word = 0x0101010101010101ull * static_cast(indent_char); + char* const d = write_buffer.data() + write_buffer_pos; + std::memcpy(d, s, length); + std::memcpy(d + length, &word, sizeof(word)); + std::memcpy(d + length + 8, &word, sizeof(word)); + std::memcpy(d + length + 16, &word, sizeof(word)); + std::memcpy(d + length + 24, &word, sizeof(word)); + write_buffer_pos += length + indent; + return; + } + put_literal(s); + put_indent(indent); + } + /*! @brief append a string literal to the write buffer