From 676ff8ff96984d510d773acb69fb40c183f01012 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Wed, 3 Jun 2026 06:07:02 -0700 Subject: [PATCH 1/2] Avoid redundant dynamic_pointer_cast in YogaLayoutableShadowNode clone (#57019) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: The clone constructor of `YogaLayoutableShadowNode` rebuilt the internal `yogaLayoutableChildren_` vector on every clone by walking every child with `dynamic_pointer_cast`. When the cloned node inherits its children list from the source (`!fragment.children`) the result is identical to the source's vector, so copy it directly instead — no RTTI required. Also `reserve()` the vector to its upper bound at the top of `updateYogaChildren()` so the rebuild on the new-children path no longer reallocates as it grows. Both changes are pure CPU-time optimizations on a hot path that shows up heavily in animation-driven tree clones; behaviour is unchanged. Changelog: [Internal] Reviewed By: christophpurrer, lenaic Differential Revision: D106287171 --- .../view/YogaLayoutableShadowNode.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp index 2fe7f96c02ab..8785963fd646 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp @@ -114,11 +114,19 @@ YogaLayoutableShadowNode::YogaLayoutableShadowNode( "Yoga node must inherit dirty flag."); #endif if (!getTraits().check(ShadowNodeTraits::Trait::LeafYogaNode)) { - for (auto& child : getChildren()) { - if (auto layoutableChild = - std::dynamic_pointer_cast( - child)) { - yogaLayoutableChildren_.push_back(std::move(layoutableChild)); + if (!fragment.children) { + // Children unchanged - copy the filtered list directly from the source + // node, avoiding expensive dynamic_pointer_cast on every child. + yogaLayoutableChildren_ = + static_cast(sourceShadowNode) + .yogaLayoutableChildren_; + } else { + for (auto& child : getChildren()) { + if (auto layoutableChild = + std::dynamic_pointer_cast( + child)) { + yogaLayoutableChildren_.push_back(std::move(layoutableChild)); + } } } } @@ -349,6 +357,7 @@ void YogaLayoutableShadowNode::updateYogaChildren() { yogaNode_.setChildren({}); yogaLayoutableChildren_.clear(); + yogaLayoutableChildren_.reserve(getChildren().size()); for (size_t i = 0; i < getChildren().size(); i++) { if (auto yogaLayoutableChild = From fd57e39f71ecd584849e0ce7d9d7cc431d22493f Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Wed, 3 Jun 2026 06:07:02 -0700 Subject: [PATCH 2/2] Skip initial yogaLayoutableChildren_ build when fragment.children is set (#57020) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: On the `fragment.children` path, the clone constructor used to iterate every child with `dynamic_pointer_cast` only to have `updateYogaChildren()` clear and rebuild the same vector a few lines later. Drop the initial loop — `updateYogaChildren()` is the single source of truth on this path. `updateYogaChildrenOwnersIfNeeded()` runs in between but iterates `yogaNode_.getChildren()` (inherited from the source's Yoga node copy) rather than `yogaLayoutableChildren_`, so leaving the vector empty here is safe. Changelog: [Internal] Reviewed By: lenaic, christophpurrer Differential Revision: D107076026 --- .../view/YogaLayoutableShadowNode.cpp | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp index 8785963fd646..62fcfb42928c 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp @@ -113,22 +113,15 @@ YogaLayoutableShadowNode::YogaLayoutableShadowNode( .yogaNode_) == YGNodeIsDirty(&yogaNode_) && "Yoga node must inherit dirty flag."); #endif - if (!getTraits().check(ShadowNodeTraits::Trait::LeafYogaNode)) { - if (!fragment.children) { - // Children unchanged - copy the filtered list directly from the source - // node, avoiding expensive dynamic_pointer_cast on every child. - yogaLayoutableChildren_ = - static_cast(sourceShadowNode) - .yogaLayoutableChildren_; - } else { - for (auto& child : getChildren()) { - if (auto layoutableChild = - std::dynamic_pointer_cast( - child)) { - yogaLayoutableChildren_.push_back(std::move(layoutableChild)); - } - } - } + if (!getTraits().check(ShadowNodeTraits::Trait::LeafYogaNode) && + !fragment.children) { + // Children unchanged: copy the filtered list directly from the source, + // skipping per-child dynamic_pointer_cast. When fragment.children is set, + // updateYogaChildren() below rebuilds the vector from the new children + // list — populating it here would be immediately discarded. + yogaLayoutableChildren_ = + static_cast(sourceShadowNode) + .yogaLayoutableChildren_; } YGConfigConstRef previousConfig =