Repository navigation
Speed up flatten(), unflatten(), diff() and patch() further - #5886
Merged
Merged
Conversation
…members in one pass A patch from diff() lists its operations in document order, so consecutive pointers share most of their reference tokens. resolve_plain() now keeps the values along the pointer it resolved last and resumes from the deepest one that is also a prefix of the next pointer, instead of walking every pointer from the root. The values stay valid because a plain operation only changes the target (replace) or its parent (add, remove), and the general operations reset the cache. The "~" check covers only the part of the pointer that is resolved anew. The members "op", "path", "value", and "from" of an operation are found in one pass over its object instead of up to four lookups (for key types other than string_t, by lookup). patch() on the diff of every eighth scalar (instructions on macOS, Linux Ir GCC 13 / Clang 18): canada 85.8M -> 67.7M, 67.4M -> 42.6M / 70.0M -> 43.6M; citm_catalog 28.7M -> 27.1M, 20.7M -> 18.1M / 20.7M -> 18.0M; twitter 14.3M -> 13.2M, 11.4M -> 9.5M / 11.3M -> 9.4M. Exceptions and results are unchanged. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Find the end of a reference token and reject an escape in one loop over the characters instead of two library calls (find_first_of, memchr), and parse an array index straight from the pointer instead of copying the token first. An index that is not a plain number of up to 18 digits (9 for a 32-bit size_type) is left to json_pointer, which reports it as before. patch() on the diff of every eighth scalar (instructions on macOS, Linux Ir GCC 13 / Clang 18): canada 67.7M -> 63.7M, 42.6M -> 38.4M / 43.6M -> 40.0M; citm_catalog 27.1M -> 26.8M, 18.1M -> 17.7M / 18.0M -> 17.7M; twitter unchanged. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
…rst child 0 in unflatten() Array elements are addressed by a plain index of up to 9 digits in nearly all flattened documents. Parse it straight from the key instead of copying the token into a string and converting that. A new container whose first child has the token 0 is an array by definition, so the hash set of array prefixes is only consulted for other first children. unflatten() (instructions on macOS, Linux Ir GCC 13 / Clang 18): canada 188.4M -> 161.1M, 151.3M -> 128.1M / 162.3M -> 138.0M; citm_catalog 45.0M -> 43.3M, 40.3M -> 38.9M / 41.1M -> 39.6M; twitter unchanged. Output and exceptions are identical to develop over the differential check. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
flatten() appended the entries to an ordered_map one by one, so its vector grew by doubling and moved every entry about once in total. The number of entries is known after a walk over the value that only counts (it needs no path strings), so objects with a reserve() are given that capacity first. std::map has no reserve() and is unchanged. flatten() of an ordered_json (instructions on macOS, Linux Ir GCC 13 / Clang 18): citm_catalog 36.8M -> 23.1M, 32.7M -> 17.1M / 31.7M -> 16.8M; twitter 20.5M -> 13.6M, 17.6M -> 10.0M / 17.0M -> 9.8M. Output is identical. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
diff_operation() created each operation through three operator[] calls and assignments. The keys "op", "path" and "value" are distinct and already in the order of std::map, so they are added with the positional insert that flatten() uses, constructing the values in place. diff() (instructions on macOS, Linux Ir GCC 13 / Clang 18): canada 85.3M -> 82.0M, 51.1M -> 50.7M / 55.3M -> 50.2M; citm_catalog 22.0M -> 21.5M, 14.3M -> 14.2M / 14.9M -> 14.2M; twitter 12.4M -> 12.0M, 7.5M unchanged / 8.0M -> 7.5M. Output is identical. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
…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 <mail@nlohmann.me>
Replacing the path of the previous child by "/<index>" took a resize, a push_back and an append. For array elements the path is now resized once to its final length and the slash and the digits are written into it. Object keys keep the three calls: a version that also wrote them directly made flatten() of twitter 4 % slower in cycles at equal instructions. flatten() (instructions on macOS, Linux Ir GCC 13 / Clang 18): canada 172.1M -> 164.7M, 123.8M -> 120.6M / 121.2M -> 118.9M; citm_catalog 40.4M -> 39.5M, 30.1M -> 29.6M / 30.0M -> 29.6M; twitter unchanged. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
object_member() looked a key up and, if it was missing, inserted it with operator[], which searches again. Objects that can find the position of a key and insert there (std::map) now do both with one search; other object types are unchanged. A key that exists is not copied, as before. unflatten() (instructions on macOS, Linux Ir GCC 13 / Clang 18): twitter 25.8M -> 24.2M, 24.9M -> 21.8M / 24.8M -> 21.5M; citm_catalog 43.4M -> 43.5M, 38.9M -> 37.0M / 39.6M -> 37.5M; canada unchanged. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
An array that unflatten() creates grows by one element at a time, so an array of two elements (a coordinate pair, a range) was allocated, grown and freed once more than needed. New arrays get room for two elements first, for array types that have reserve(). Arrays of two or more elements need no more memory than before; an array of one element has room for one more. unflatten() (instructions on macOS, Linux Ir GCC 13 / Clang 18): canada 161.9M -> 141.2M, 128.3M -> 117.9M / 138.0M -> 128.6M; citm_catalog 43.5M -> 42.8M, 37.0M -> 36.8M / 37.5M -> 37.3M; twitter unchanged. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
<windows.h> (rpcndr.h) defines small as char, so test-windows_h did not compile with MSVC. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
- diff_target_values holds its inline values in a std::array (a type with an empty user-provided constructor, as the serializer's buffers, so that -Weffc++ is met and they stay uninitialized) and declares its move members as deleted - auto for an iterator and a cast, and parentheses around a % json::diff() instructions are unchanged within noise (Apple clang, twitter and citm_catalog). Signed-off-by: Niels Lohmann <mail@nlohmann.me>
gregmarr
approved these changes
Oct 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A follow-up to #5861.
Instructions, macOS / Linux GCC 13 / Linux Clang 18:
resolve_plain()unflatten()ordered_jsoninflatten()onceflatten()with a single resizeunflatten()andj[json_pointer]unflatten()createsThe first commit also adds an
is_dash()helper. GCC turnedreference_token == "-"into astrlenandmemcmpcall:contains()was 15 % slower before the helper and is now unchanged.Numbers
macOS timing vs develop:
No benchmark is slower than +0.6 % (noise).
Validation
JSON_DIAGNOSTICSandJSON_DIAGNOSTIC_POSITIONSbuilds:jsonandordered_json;single_includeis a freshmake amalgamate.Public API
No breaking changes.
Written by Claude Code on behalf of @nlohmann.
🤖 Generated with Claude Code