Repository navigation
Speed up container destruction, iterator access and diff() - #5835
Conversation
| { | ||
| JSON_ASSERT(m_object != nullptr); | ||
|
|
||
| if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array)) |
There was a problem hiding this comment.
Why duplicate the code from the switch here? Was this supposed to be a move? If so, why?
There was a problem hiding this comment.
Written by Claude Code on behalf of @nlohmann. You're right, the duplication was unintended: it is now a move (commit 7cc6f32). The array/object branches sit in front of the switch so the hot path inlines (range-for over an array 2.5 → 1.3 ns/element); the switch bodies for those cases are gone, and what remained only handled primitives, so it is now plain code. No behaviour change.
| { | ||
| JSON_ASSERT(m_object != nullptr); | ||
|
|
||
| if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array)) |
There was a problem hiding this comment.
Why duplicate the code from the switch here? Was this supposed to be a move? If so, why?
There was a problem hiding this comment.
Written by Claude Code on behalf of @nlohmann. You're right, the duplication was unintended: it is now a move (commit 7cc6f32). The array/object branches sit in front of the switch so the hot path inlines (range-for over an array 2.5 → 1.3 ns/element); the switch bodies for those cases are gone, and what remained only handled primitives, so it is now plain code. No behaviour change.
| { | ||
| JSON_ASSERT(m_object != nullptr); | ||
|
|
||
| if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array)) |
There was a problem hiding this comment.
Why duplicate the code from the switch here? Was this supposed to be a move? If so, why?
There was a problem hiding this comment.
Written by Claude Code on behalf of @nlohmann. You're right, the duplication was unintended: it is now a move (commit 7cc6f32). The array/object branches sit in front of the switch so the hot path inlines (range-for over an array 2.5 → 1.3 ns/element); the switch bodies for those cases are gone, and what remained only handled primitives, so it is now plain code. No behaviour change.
| return true; | ||
| } | ||
|
|
||
| if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array)) |
There was a problem hiding this comment.
Why duplicate the code from the switch here? Was this supposed to be a move? If so, why?
There was a problem hiding this comment.
Written by Claude Code on behalf of @nlohmann. You're right, the duplication was unintended: it is now a move (commit 7cc6f32). The array/object branches sit in front of the switch so the hot path inlines (range-for over an array 2.5 → 1.3 ns/element); the switch bodies for those cases are gone, and what remained only handled primitives, so it is now plain code. No behaviour change.
|
Written by Claude Code on behalf of @nlohmann. Pushed f4fd1e1: |
7cc6f32 to
af4e6e2
Compare
af4e6e2 to
b115675
Compare
82250cb to
99ae8b9
Compare
47e0c77 to
0eb415c
Compare
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
The pointer-reversing walk in destroy_container() avoids the call stack but moves a value around for every container it visits. Recurse for the first recursion_depth_limit() levels instead - no allocation, no thread-local, a bounded frame - and hand anything deeper to the walk. Destroy twitter 0.49 -> 0.36 ms, citm 0.80 -> 0.74 ms (3.12.0: 0.47 / 1.00). Signed-off-by: Niels Lohmann <mail@nlohmann.me>
0eb415c to
8aeb14e
Compare
The comment block about destroying the tree without recursion was indented one level too deep since #5835. make amalgamate reformatted it with astyle, leaving an unrelated diff in every branch. single_include is unchanged. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
- unit-iterators1 (#5835) converted a json element to int implicitly, which does not compile with JSON_USE_IMPLICIT_CONVERSIONS=0. - unit-testsuites parses a number followed by a NUL byte, which JSON_STRICT_NUL_HANDLING rejects; leave the NUL out in strict mode. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Three API-neutral speedups found in a performance survey of the non-parse API.
all_children_leaves); the walk stays non-recursive and allocation-free (tested withulimit -s 512on 1M–2M deep nesting). Object of 100k ints: 41.8 → 17.4 ns/member (std::map dtor itself: 15 ns); flat array of 1M doubles 2.1 → 1.8 ns; documents unchanged.iter_implfast paths: array, then object, are checked before the type switch in++,--(prefix and postfix),*,->(arrays and objects) and==;<,+=and-stay on the switch; the cold throw in==moved to aNEVER_INLINEmember. Range-for over an array 2.5 → 1.3 ns/element,items()over an array 2.2 → 1.0, range-for over an object 4.9 → 4.1.diff(): onefindper source key (storingvector<const basic_json*>), target pass skipped when no keys were added, "add" operations built lazily, children compared with==before their path is built; same in the iterative (deep) path.diff(a, a + 1% changes): citm 1.09 → 0.70 ms, twitter 0.92 → 0.41 ms;diff({}, citm)3.18 → 1.62 ms.Reverse range iteration (
rbegin()..rend(), Apple clang -O2 -DNDEBUG, best of 15): over a 1M-element array develop 3.5 ns/element, PR before this update 1.9, after 1.9 (the--fast path adds nothing measurable there, the reverse iterator was already well inlined); over a 200k-member object develop 8.2, before 6.2, after 6.5 (within noise).Tests added: reverse iteration (
--,rbegin/rend) and object->forjsonandordered_json, comparing iterators of different containers (unit-iterators1);diff()round trips with reordered keys and nested add/remove/replace for both types (unit-json_patch); destruction of empty, mixed and 5000-deep containers for both types (unit-constructor2). Run with-fsanitize=address,undefined, also with-DJSON_DIAGNOSTICS=1for diagnostics.Verification: differential fuzz of
diff(40k random pairs,jsonandordered_json, keys with~and/, reordered keys, deep nesting through the iterative path) is byte-identical to develop andpatch(diff)round-trips. Unit tests iterators1/2, items, class_iterator, class_const_iterator, json_patch, regression2, class_parser, allocator, ordered_json/ordered_map, custom-object-key-type, diagnostics, merge_patch pass, also with-DJSON_DIAGNOSTICS=1.Breaking changes
None. No public interface, ABI or layout change;
diff()output is identical.🤖 Generated with Claude Code