From 7521d0436b3d3013ab7e0c81440a3fb1e89e31f0 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 16 Oct 2026 21:33:37 +0200 Subject: [PATCH] Keep the target values of a diffed object of up to 16 members on the stack diff_unequal() allocated a vector with the values in target of the members of every object it diffed member by member. Objects with up to 16 members, which are most of them, now use space in the stack frame, and larger ones a vector as before. The storage is chosen once, when its size is reserved, so that adding and reading a value needs no check of which one is in use. diff() over the previous commit (instructions on macOS, Linux Ir GCC 13 / Clang 18): citm_catalog 21.5M -> 20.0M, 14.2M -> 13.5M / 14.2M -> 13.7M; twitter 12.0M -> 11.7M, 7.5M -> 7.3M / 7.5M -> 7.4M; canada 82.0M -> 81.5M. Signed-off-by: Niels Lohmann --- include/nlohmann/json.hpp | 49 ++++++++++++++++++++++++++++++-- single_include/nlohmann/json.hpp | 49 ++++++++++++++++++++++++++++++-- 2 files changed, 94 insertions(+), 4 deletions(-) diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 9dae01b4f..a9b6fe0b0 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -7476,6 +7476,50 @@ public: diff_operation(result, "add", path, &value); } + /// @brief the values in the target of the members of a source object, for the + /// objects of up to 16 members without allocating + /// + /// reserve() must be called with the number of values that are added. + class diff_target_values + { + public: + diff_target_values() = default; + diff_target_values(const diff_target_values&) = delete; + diff_target_values& operator=(const diff_target_values&) = delete; + ~diff_target_values() = default; + + void reserve(const std::size_t n) + { + if (n > inline_size) + { + large.resize(n); + items = large.data(); + } + } + + void push_back(const basic_json* value) + { + items[count++] = value; + } + + std::size_t size() const noexcept + { + return count; + } + + const basic_json* operator[](const std::size_t i) const noexcept + { + return items[i]; + } + + private: + static constexpr std::size_t inline_size = 16; + std::size_t count = 0; + const basic_json** items = small; + std::vector large{}; // NOLINT(readability-redundant-member-init) + const basic_json* small[inline_size]; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init) + }; + /// @brief append "/" and the decimal representation of @a index to @a path static void diff_append_index(string_t& path, std::size_t index) { @@ -7556,8 +7600,9 @@ public: operations that remove every member of @a source and add every member of @a target to @a result, and returns false. */ + template static bool diff_object_keys(basic_json& result, const basic_json& source, const basic_json& target, - const string_t& path, std::vector& target_values, + const string_t& path, TargetValues& target_values, std::vector& added) { // first pass: one lookup per source key @@ -7771,7 +7816,7 @@ public: case value_t::object: { - std::vector target_values; + diff_target_values target_values; std::vector added; if (diff_object_keys(result, source, target, path, target_values, added)) { diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 9bf340c58..6917083c5 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -39932,6 +39932,50 @@ public: diff_operation(result, "add", path, &value); } + /// @brief the values in the target of the members of a source object, for the + /// objects of up to 16 members without allocating + /// + /// reserve() must be called with the number of values that are added. + class diff_target_values + { + public: + diff_target_values() = default; + diff_target_values(const diff_target_values&) = delete; + diff_target_values& operator=(const diff_target_values&) = delete; + ~diff_target_values() = default; + + void reserve(const std::size_t n) + { + if (n > inline_size) + { + large.resize(n); + items = large.data(); + } + } + + void push_back(const basic_json* value) + { + items[count++] = value; + } + + std::size_t size() const noexcept + { + return count; + } + + const basic_json* operator[](const std::size_t i) const noexcept + { + return items[i]; + } + + private: + static constexpr std::size_t inline_size = 16; + std::size_t count = 0; + const basic_json** items = small; + std::vector large{}; // NOLINT(readability-redundant-member-init) + const basic_json* small[inline_size]; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init) + }; + /// @brief append "/" and the decimal representation of @a index to @a path static void diff_append_index(string_t& path, std::size_t index) { @@ -40012,8 +40056,9 @@ public: operations that remove every member of @a source and add every member of @a target to @a result, and returns false. */ + template static bool diff_object_keys(basic_json& result, const basic_json& source, const basic_json& target, - const string_t& path, std::vector& target_values, + const string_t& path, TargetValues& target_values, std::vector& added) { // first pass: one lookup per source key @@ -40227,7 +40272,7 @@ public: case value_t::object: { - std::vector target_values; + diff_target_values target_values; std::vector added; if (diff_object_keys(result, source, target, path, target_values, added)) {