perf: Move entries off NodePathStack instead of copying them - #7945
Draft
bthomee wants to merge 1 commit into
Draft
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR reduces SHAMap traversal overhead by moving NodePathStack entries out (instead of copying then popping), avoiding unnecessary refcount churn on the ledger write path. It also hardens assumptions around inner/leaf node types with explicit runtime checks, and adds a regression test for updateGiveItem on absent keys.
Changes:
- Add
NodePathStack::release()to pop-and-return the top entry via move, and update hot call sites to use it. - Add rvalue overloads for
intr_ptr::{static,dynamic}PointerCastsostd::moveactually enables move semantics forSharedIntrusive. - Add a unit test verifying
updateGiveItemreturnsfalsewhen the target key is absent (top-of-stack can be an inner node).
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/tests/libxrpl/shamap/SHAMap.cpp | Adds regression coverage for updateGiveItem when walkTowardsKey ends on an inner node for an absent key. |
| src/libxrpl/shamap/SHAMap.cpp | Switches several stack consumers to release()/reference binding, adds non-debug-safe node-type checks, and restores real move semantics at ownership-discard sites. |
| include/xrpl/shamap/SHAMap.h | Introduces NodePathStack::release() and improves walkTowardsKey parameter documentation. |
| include/xrpl/basics/IntrusivePointer.h | Adds rvalue overloads for staticPointerCast/dynamicPointerCast to avoid unintended copies when moving SharedIntrusive. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
680
to
684
| if (stack.empty()) | ||
| Throw<SHAMapMissingNode>(type_, id); | ||
|
|
||
| auto leaf = intr_ptr::dynamicPointerCast<SHAMapLeafNode>(stack.top().first); | ||
| stack.pop(); | ||
| auto leaf = intr_ptr::dynamicPointerCast<SHAMapLeafNode>(stack.release().first); | ||
|
|
bthomee
force-pushed
the
bthomee/shamap-stack-07-nodepathstack-fail-closed
branch
from
August 3, 2026 23:25
d69ec58 to
fc4d854
Compare
bthomee
force-pushed
the
bthomee/shamap-stack-08-move-entries-off-nodepathstack
branch
from
August 3, 2026 23:25
e1f047e to
3d6940c
Compare
bthomee
force-pushed
the
bthomee/shamap-stack-07-nodepathstack-fail-closed
branch
from
August 4, 2026 00:12
fc4d854 to
8fe795c
Compare
bthomee
force-pushed
the
bthomee/shamap-stack-08-move-entries-off-nodepathstack
branch
from
August 4, 2026 00:12
3d6940c to
03ae83f
Compare
Five sites copied the top entry out and then popped it. `SHAMapTreeNodePtr` is refcounted, so each copy bumped its atomic strong-ref count; `release()` moves the pointer out instead, a plain pointer swap with no atomic. `SHAMapNodeID` also derives from `CountedObject`, but `CountedObject` declares no move constructor, so moving a `SHAMapNodeID` still runs its copy constructor; `release()` saves nothing on the ID half of the pair. `dirtyUp` and `delItem` walk up to 64 levels per insert or delete on the ledger write path, so this removes up to 64 atomic operations per call, not 128. The two sites that read without popping now bind a reference rather than copying. `dirtyUp` and `delItem` both drop a `dynamicPointerCast`/null check for a static cast, on the reasoning that by the time either receives the stack, `addGiveItem`/`updateGiveItem` have already consumed the terminal leaf entry via `release()`, so every remaining entry is provably an inner node. That reasoning holds today, but an `XRPL_ASSERT` is a no-op under `NDEBUG`, so both get a real `UNREACHABLE`-guarded check instead: a release build that somehow violated the invariant would otherwise write through a misinterpreted node via `setChild`, silent memory corruption in place of the clean crash `dynamicPointerCast` used to produce. `updateGiveItem`'s own cast needed the same treatment for a different reason: an absent tag leaves an inner node on top of the stack, and the cast that followed the assertion there would have reinterpreted an `SHAMapInnerNode` as a `SHAMapLeafNode`. Replaced with `if (!top->isLeaf()) return false;`, pinned by a regression test. The `std::move` these sites previously applied to `staticPointerCast`/`dynamicPointerCast` was dropped rather than fixed: both only had a `TT const&` overload, so the move bound to that const ref and copied anyway, silently defeating the `SHAMapTreeNodePtr` refcount saving described above. Adds the missing rvalue overload to each, tied to `SharedIntrusive<TT>&&` rather than a bare `TT&&` so it cannot also bind to an lvalue in preference to the const-ref overload, and restores `std::move` at the three call sites that own a soon-to-be-discarded pointer.
bthomee
force-pushed
the
bthomee/shamap-stack-07-nodepathstack-fail-closed
branch
from
August 4, 2026 00:49
8fe795c to
a7dcbae
Compare
bthomee
force-pushed
the
bthomee/shamap-stack-08-move-entries-off-nodepathstack
branch
from
August 4, 2026 00:49
03ae83f to
f357484
Compare
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.
Part 8/8 of a stack (top). Base: part 7 (
bthomee/shamap-stack-07-nodepathstack-fail-closed).Five sites copied the top entry out and then popped it.
SHAMapTreeNodePtrisrefcounted, so each copy bumped its atomic strong-ref count;
release()movesthe pointer out instead, a plain pointer swap with no atomic.
SHAMapNodeIDalso derives from
CountedObject, butCountedObjectdeclares no moveconstructor, so moving a
SHAMapNodeIDstill runs its copy constructor;release()saves nothing on the ID half of the pair.dirtyUpanddelItemwalk up to 64 levels per insert or delete on the ledger write path, so this
removes up to 64 atomic operations per call, not 128. The two sites that read
without popping now bind a reference rather than copying.
dirtyUpanddelItemboth drop adynamicPointerCast/null check for astatic cast, on the reasoning that by the time either receives the stack,
addGiveItem/updateGiveItemhave already consumed the terminal leaf entryvia
release(), so every remaining entry is provably an inner node. Thatreasoning holds today, but an
XRPL_ASSERTis a no-op underNDEBUG, soboth get a real
UNREACHABLE-guarded check instead: a release build thatsomehow violated the invariant would otherwise write through a
misinterpreted node via
setChild, silent memory corruption in place of theclean crash
dynamicPointerCastused to produce.updateGiveItem's owncast needed the same treatment for a different reason: an absent tag leaves
an inner node on top of the stack, and the cast that followed the assertion
there would have reinterpreted an
SHAMapInnerNodeas aSHAMapLeafNode.Replaced with
if (!top->isLeaf()) return false;, pinned by a regressiontest.
The
std::movethese sites previously applied tostaticPointerCast/dynamicPointerCastwas dropped rather than fixed: bothonly had a
TT const&overload, so the move bound to that const ref andcopied anyway, silently defeating the
SHAMapTreeNodePtrrefcount savingdescribed above. Adds the missing rvalue overload to each, tied to
SharedIntrusive<TT>&&rather than a bareTT&&so it cannot also bind toan lvalue in preference to the const-ref overload, and restores
std::moveat the three call sites that own a soon-to-be-discarded pointer.