From 7c3888fe42048b8aac4b0c749634967689c327f5 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 16 Oct 2026 21:23:58 +0200 Subject: [PATCH] Write the scalar elements of a container without entering dump_internal() Every element of an array or object went through dump_internal(), whose frame holds the state for descending into a container (callee-saved registers, indentation, depth). A scalar, which is what most elements are, needs none of it, and paid for the entry and exit regardless. The loops now call dump_scalar() directly for anything that is not an object or an array, and dump_internal() for those only. Apple M1, Apple clang, retired instructions per dump(), on top of the previous change: small object 2997 -> 2712 (-10 %) signed_ints 276M -> 252M (-9 %) floats 323M -> 301M (-7 %) jeopardy 501M -> 462M (-8 %) citm_catalog 5.96M -> 5.61M (-6 %) twitter 4.40M -> 4.12M (-6 %) canada 39.2M -> 37.0M (-5 %) Signed-off-by: Niels Lohmann --- include/nlohmann/detail/output/serializer.hpp | 37 +++++++++++++++---- single_include/nlohmann/json.hpp | 37 +++++++++++++++---- 2 files changed, 58 insertions(+), 16 deletions(-) diff --git a/include/nlohmann/detail/output/serializer.hpp b/include/nlohmann/detail/output/serializer.hpp index b97a6efb5..d1c78346e 100644 --- a/include/nlohmann/detail/output/serializer.hpp +++ b/include/nlohmann/detail/output/serializer.hpp @@ -178,7 +178,7 @@ class serializer { put_indent(new_indent); put_quoted(i->first, "\": "); - dump_internal(i->second, new_indent, depth + 1); + dump_element(i->second, new_indent, depth + 1); put_literal(",\n"); } @@ -187,7 +187,7 @@ class serializer JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend()); put_indent(new_indent); put_quoted(i->first, "\": "); - dump_internal(i->second, new_indent, depth + 1); + dump_element(i->second, new_indent, depth + 1); put_char('\n'); put_indent(current_indent); @@ -202,7 +202,7 @@ class serializer for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i) { put_quoted(i->first, "\":"); - dump_internal(i->second, current_indent, depth + 1); + dump_element(i->second, current_indent, depth + 1); put_char(','); } @@ -210,7 +210,7 @@ class serializer JSON_ASSERT(i != val.m_data.m_value.object->cend()); JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend()); put_quoted(i->first, "\":"); - dump_internal(i->second, current_indent, depth + 1); + dump_element(i->second, current_indent, depth + 1); put_char('}'); } @@ -244,14 +244,14 @@ class serializer i != val.m_data.m_value.array->cend() - 1; ++i) { put_indent(new_indent); - dump_internal(*i, new_indent, depth + 1); + dump_element(*i, new_indent, depth + 1); put_literal(",\n"); } // last element JSON_ASSERT(!val.m_data.m_value.array->empty()); put_indent(new_indent); - dump_internal(val.m_data.m_value.array->back(), new_indent, depth + 1); + dump_element(val.m_data.m_value.array->back(), new_indent, depth + 1); put_char('\n'); put_indent(current_indent); @@ -265,13 +265,13 @@ class serializer for (auto i = val.m_data.m_value.array->cbegin(); i != val.m_data.m_value.array->cend() - 1; ++i) { - dump_internal(*i, current_indent, depth + 1); + dump_element(*i, current_indent, depth + 1); put_char(','); } // last element JSON_ASSERT(!val.m_data.m_value.array->empty()); - dump_internal(val.m_data.m_value.array->back(), current_indent, depth + 1); + dump_element(val.m_data.m_value.array->back(), current_indent, depth + 1); put_char(']'); } @@ -294,6 +294,27 @@ class serializer } private: + /*! + @brief serialize an element of a container + + Most elements are scalars, which need none of the state dump_internal() keeps + for a container; writing them from here spares each the entry and exit of + that function. + */ + JSON_HEDLEY_ALWAYS_INLINE void dump_element(const BasicJsonType& val, + const std::size_t current_indent, + const std::size_t depth) + { + if (val.m_data.m_type == value_t::object || val.m_data.m_type == value_t::array) + { + dump_internal(val, current_indent, depth); + } + else + { + dump_scalar(val, current_indent); + } + } + /*! @brief write out @a val and everything below it without the call stack diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 1dbf1fd48..346cfebd0 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -30322,7 +30322,7 @@ class serializer { put_indent(new_indent); put_quoted(i->first, "\": "); - dump_internal(i->second, new_indent, depth + 1); + dump_element(i->second, new_indent, depth + 1); put_literal(",\n"); } @@ -30331,7 +30331,7 @@ class serializer JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend()); put_indent(new_indent); put_quoted(i->first, "\": "); - dump_internal(i->second, new_indent, depth + 1); + dump_element(i->second, new_indent, depth + 1); put_char('\n'); put_indent(current_indent); @@ -30346,7 +30346,7 @@ class serializer for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i) { put_quoted(i->first, "\":"); - dump_internal(i->second, current_indent, depth + 1); + dump_element(i->second, current_indent, depth + 1); put_char(','); } @@ -30354,7 +30354,7 @@ class serializer JSON_ASSERT(i != val.m_data.m_value.object->cend()); JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend()); put_quoted(i->first, "\":"); - dump_internal(i->second, current_indent, depth + 1); + dump_element(i->second, current_indent, depth + 1); put_char('}'); } @@ -30388,14 +30388,14 @@ class serializer i != val.m_data.m_value.array->cend() - 1; ++i) { put_indent(new_indent); - dump_internal(*i, new_indent, depth + 1); + dump_element(*i, new_indent, depth + 1); put_literal(",\n"); } // last element JSON_ASSERT(!val.m_data.m_value.array->empty()); put_indent(new_indent); - dump_internal(val.m_data.m_value.array->back(), new_indent, depth + 1); + dump_element(val.m_data.m_value.array->back(), new_indent, depth + 1); put_char('\n'); put_indent(current_indent); @@ -30409,13 +30409,13 @@ class serializer for (auto i = val.m_data.m_value.array->cbegin(); i != val.m_data.m_value.array->cend() - 1; ++i) { - dump_internal(*i, current_indent, depth + 1); + dump_element(*i, current_indent, depth + 1); put_char(','); } // last element JSON_ASSERT(!val.m_data.m_value.array->empty()); - dump_internal(val.m_data.m_value.array->back(), current_indent, depth + 1); + dump_element(val.m_data.m_value.array->back(), current_indent, depth + 1); put_char(']'); } @@ -30438,6 +30438,27 @@ class serializer } private: + /*! + @brief serialize an element of a container + + Most elements are scalars, which need none of the state dump_internal() keeps + for a container; writing them from here spares each the entry and exit of + that function. + */ + JSON_HEDLEY_ALWAYS_INLINE void dump_element(const BasicJsonType& val, + const std::size_t current_indent, + const std::size_t depth) + { + if (val.m_data.m_type == value_t::object || val.m_data.m_type == value_t::array) + { + dump_internal(val, current_indent, depth); + } + else + { + dump_scalar(val, current_indent); + } + } + /*! @brief write out @a val and everything below it without the call stack