From 1fe79ee26c4f05566e9fa4792bb8ce4904c42cc0 Mon Sep 17 00:00:00 2001 From: Kamil Paradowski Date: Wed, 1 Apr 2026 12:09:11 -0700 Subject: [PATCH] Optimize style value resolution with handle-based approach Summary: This PR is an optimization attempt during work on the `calc()` feature: https://github.com/facebook/yoga/pull/1874. It aims to reduce the regression effect on layout operations after extending the size of `StyleLength` and `StyleSizeLength` classes. ## Changelog: This PR introduces a private `resolve(StyleValueHandle handle, float referenceLength)` function to `Style.h` that resolves its property values directly from `StyleValueHandle` and skips creating intermediate objects. I am aware that this duplicates the resolving functionality that already exists in `StyleLength.h` and `StyleSizeLength.h`, and may not be the cleanest solution. Optimization could go even further and totally drop the `resolve()` function from `StyleLength.h` and `StyleSizeLength.h`, and replace all usages with the newly introduced functions. But I think the performance boost is not high enough to justify that. Also it won't be the best API for Yoga library consumers - that's why I decided to keep them. However, I am opening this PR to start a discussion and would really like to hear opinions about it. ## Benchmarks: Results were averaged over 20 runs. This does not bring much gain in terms of performance when compared to the main branch. image However, it introduces a significant boost for the calc() and YGValueDynamic work. Results before these optimizations: image And after: image cc NickGerleman X-link: https://github.com/facebook/yoga/pull/1922 Reviewed By: zeyap Differential Revision: D99006553 Pulled By: NickGerleman --- .../ReactCommon/yoga/yoga/style/Style.h | 167 +++++++++++------- .../yoga/yoga/style/StyleValueHandle.h | 8 + .../yoga/yoga/style/StyleValuePool.h | 10 ++ 3 files changed, 117 insertions(+), 68 deletions(-) diff --git a/packages/react-native/ReactCommon/yoga/yoga/style/Style.h b/packages/react-native/ReactCommon/yoga/yoga/style/Style.h index eedeaba2e50b..37c0ba2a9a84 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/style/Style.h +++ b/packages/react-native/ReactCommon/yoga/yoga/style/Style.h @@ -294,8 +294,12 @@ class YG_EXPORT Style { Dimension axis, float referenceLength, float ownerWidth) const { - FloatOptional value = minDimension(axis).resolve(referenceLength); - if (boxSizing() == BoxSizing::BorderBox) { + const auto handle = minDimensions_[yoga::to_underlying(axis)]; + if (handle.isUndefined()) { + return FloatOptional{}; + } + FloatOptional value = resolve(handle, referenceLength); + if (boxSizing() == BoxSizing::BorderBox || !value.isDefined()) { return value; } @@ -319,8 +323,12 @@ class YG_EXPORT Style { Dimension axis, float referenceLength, float ownerWidth) const { - FloatOptional value = maxDimension(axis).resolve(referenceLength); - if (boxSizing() == BoxSizing::BorderBox) { + const auto handle = maxDimensions_[yoga::to_underlying(axis)]; + if (handle.isUndefined()) { + return FloatOptional{}; + } + FloatOptional value = resolve(handle, referenceLength); + if (boxSizing() == BoxSizing::BorderBox || !value.isDefined()) { return value; } @@ -409,8 +417,7 @@ class YG_EXPORT Style { FlexDirection axis, Direction direction, float axisSize) const { - return computePosition(flexStartEdge(axis), direction) - .resolve(axisSize) + return resolve(computePosition(flexStartEdge(axis), direction), axisSize) .unwrapOrDefault(0.0f); } @@ -418,8 +425,9 @@ class YG_EXPORT Style { FlexDirection axis, Direction direction, float axisSize) const { - return computePosition(inlineStartEdge(axis, direction), direction) - .resolve(axisSize) + return resolve( + computePosition(inlineStartEdge(axis, direction), direction), + axisSize) .unwrapOrDefault(0.0f); } @@ -427,8 +435,7 @@ class YG_EXPORT Style { FlexDirection axis, Direction direction, float axisSize) const { - return computePosition(flexEndEdge(axis), direction) - .resolve(axisSize) + return resolve(computePosition(flexEndEdge(axis), direction), axisSize) .unwrapOrDefault(0.0f); } @@ -436,8 +443,9 @@ class YG_EXPORT Style { FlexDirection axis, Direction direction, float axisSize) const { - return computePosition(inlineEndEdge(axis, direction), direction) - .resolve(axisSize) + return resolve( + computePosition(inlineEndEdge(axis, direction), direction), + axisSize) .unwrapOrDefault(0.0f); } @@ -445,8 +453,7 @@ class YG_EXPORT Style { FlexDirection axis, Direction direction, float widthSize) const { - return computeMargin(flexStartEdge(axis), direction) - .resolve(widthSize) + return resolve(computeMargin(flexStartEdge(axis), direction), widthSize) .unwrapOrDefault(0.0f); } @@ -454,8 +461,9 @@ class YG_EXPORT Style { FlexDirection axis, Direction direction, float widthSize) const { - return computeMargin(inlineStartEdge(axis, direction), direction) - .resolve(widthSize) + return resolve( + computeMargin(inlineStartEdge(axis, direction), direction), + widthSize) .unwrapOrDefault(0.0f); } @@ -463,8 +471,7 @@ class YG_EXPORT Style { FlexDirection axis, Direction direction, float widthSize) const { - return computeMargin(flexEndEdge(axis), direction) - .resolve(widthSize) + return resolve(computeMargin(flexEndEdge(axis), direction), widthSize) .unwrapOrDefault(0.0f); } @@ -472,36 +479,36 @@ class YG_EXPORT Style { FlexDirection axis, Direction direction, float widthSize) const { - return computeMargin(inlineEndEdge(axis, direction), direction) - .resolve(widthSize) + return resolve( + computeMargin(inlineEndEdge(axis, direction), direction), + widthSize) .unwrapOrDefault(0.0f); } float computeFlexStartBorder(FlexDirection axis, Direction direction) const { return maxOrDefined( - computeBorder(flexStartEdge(axis), direction).resolve(0.0f).unwrap(), + resolve(computeBorder(flexStartEdge(axis), direction), 0.0f).unwrap(), 0.0f); } float computeInlineStartBorder(FlexDirection axis, Direction direction) const { return maxOrDefined( - computeBorder(inlineStartEdge(axis, direction), direction) - .resolve(0.0f) + resolve( + computeBorder(inlineStartEdge(axis, direction), direction), 0.0f) .unwrap(), 0.0f); } float computeFlexEndBorder(FlexDirection axis, Direction direction) const { return maxOrDefined( - computeBorder(flexEndEdge(axis), direction).resolve(0.0f).unwrap(), + resolve(computeBorder(flexEndEdge(axis), direction), 0.0f).unwrap(), 0.0f); } float computeInlineEndBorder(FlexDirection axis, Direction direction) const { return maxOrDefined( - computeBorder(inlineEndEdge(axis, direction), direction) - .resolve(0.0f) + resolve(computeBorder(inlineEndEdge(axis, direction), direction), 0.0f) .unwrap(), 0.0f); } @@ -511,8 +518,7 @@ class YG_EXPORT Style { Direction direction, float widthSize) const { return maxOrDefined( - computePadding(flexStartEdge(axis), direction) - .resolve(widthSize) + resolve(computePadding(flexStartEdge(axis), direction), widthSize) .unwrap(), 0.0f); } @@ -522,8 +528,9 @@ class YG_EXPORT Style { Direction direction, float widthSize) const { return maxOrDefined( - computePadding(inlineStartEdge(axis, direction), direction) - .resolve(widthSize) + resolve( + computePadding(inlineStartEdge(axis, direction), direction), + widthSize) .unwrap(), 0.0f); } @@ -533,8 +540,7 @@ class YG_EXPORT Style { Direction direction, float widthSize) const { return maxOrDefined( - computePadding(flexEndEdge(axis), direction) - .resolve(widthSize) + resolve(computePadding(flexEndEdge(axis), direction), widthSize) .unwrap(), 0.0f); } @@ -544,8 +550,9 @@ class YG_EXPORT Style { Direction direction, float widthSize) const { return maxOrDefined( - computePadding(inlineEndEdge(axis, direction), direction) - .resolve(widthSize) + resolve( + computePadding(inlineEndEdge(axis, direction), direction), + widthSize) .unwrap(), 0.0f); } @@ -610,13 +617,13 @@ class YG_EXPORT Style { float computeGapForAxis(FlexDirection axis, float ownerSize) const { auto gap = isRow(axis) ? computeColumnGap() : computeRowGap(); - return maxOrDefined(gap.resolve(ownerSize).unwrap(), 0.0f); + return maxOrDefined(resolve(gap, ownerSize).unwrap(), 0.0f); } float computeGapForDimension(Dimension dimension, float ownerSize) const { auto gap = dimension == Dimension::Width ? computeColumnGap() : computeRowGap(); - return maxOrDefined(gap.resolve(ownerSize).unwrap(), 0.0f); + return maxOrDefined(resolve(gap, ownerSize).unwrap(), 0.0f); } bool flexStartMarginIsAuto(FlexDirection axis, Direction direction) const { @@ -709,79 +716,82 @@ class YG_EXPORT Style { }); } - Style::Length computeColumnGap() const { + StyleValueHandle computeColumnGap() const { if (gap_[yoga::to_underlying(Gutter::Column)].isDefined()) { - return pool_.getLength(gap_[yoga::to_underlying(Gutter::Column)]); + return gap_[yoga::to_underlying(Gutter::Column)]; } else { - return pool_.getLength(gap_[yoga::to_underlying(Gutter::All)]); + return gap_[yoga::to_underlying(Gutter::All)]; } } - Style::Length computeRowGap() const { + StyleValueHandle computeRowGap() const { if (gap_[yoga::to_underlying(Gutter::Row)].isDefined()) { - return pool_.getLength(gap_[yoga::to_underlying(Gutter::Row)]); + return gap_[yoga::to_underlying(Gutter::Row)]; } else { - return pool_.getLength(gap_[yoga::to_underlying(Gutter::All)]); + return gap_[yoga::to_underlying(Gutter::All)]; } } - Style::Length computeLeftEdge(const Edges& edges, Direction layoutDirection) - const { + StyleValueHandle computeLeftEdge( + const Edges& edges, + Direction layoutDirection) const { if (layoutDirection == Direction::LTR && edges[yoga::to_underlying(Edge::Start)].isDefined()) { - return pool_.getLength(edges[yoga::to_underlying(Edge::Start)]); + return edges[yoga::to_underlying(Edge::Start)]; } else if ( layoutDirection == Direction::RTL && edges[yoga::to_underlying(Edge::End)].isDefined()) { - return pool_.getLength(edges[yoga::to_underlying(Edge::End)]); + return edges[yoga::to_underlying(Edge::End)]; } else if (edges[yoga::to_underlying(Edge::Left)].isDefined()) { - return pool_.getLength(edges[yoga::to_underlying(Edge::Left)]); + return edges[yoga::to_underlying(Edge::Left)]; } else if (edges[yoga::to_underlying(Edge::Horizontal)].isDefined()) { - return pool_.getLength(edges[yoga::to_underlying(Edge::Horizontal)]); + return edges[yoga::to_underlying(Edge::Horizontal)]; } else { - return pool_.getLength(edges[yoga::to_underlying(Edge::All)]); + return edges[yoga::to_underlying(Edge::All)]; } } - Style::Length computeTopEdge(const Edges& edges) const { + StyleValueHandle computeTopEdge(const Edges& edges) const { if (edges[yoga::to_underlying(Edge::Top)].isDefined()) { - return pool_.getLength(edges[yoga::to_underlying(Edge::Top)]); + return edges[yoga::to_underlying(Edge::Top)]; } else if (edges[yoga::to_underlying(Edge::Vertical)].isDefined()) { - return pool_.getLength(edges[yoga::to_underlying(Edge::Vertical)]); + return edges[yoga::to_underlying(Edge::Vertical)]; } else { - return pool_.getLength(edges[yoga::to_underlying(Edge::All)]); + return edges[yoga::to_underlying(Edge::All)]; } } - Style::Length computeRightEdge(const Edges& edges, Direction layoutDirection) - const { + StyleValueHandle computeRightEdge( + const Edges& edges, + Direction layoutDirection) const { if (layoutDirection == Direction::LTR && edges[yoga::to_underlying(Edge::End)].isDefined()) { - return pool_.getLength(edges[yoga::to_underlying(Edge::End)]); + return edges[yoga::to_underlying(Edge::End)]; } else if ( layoutDirection == Direction::RTL && edges[yoga::to_underlying(Edge::Start)].isDefined()) { - return pool_.getLength(edges[yoga::to_underlying(Edge::Start)]); + return edges[yoga::to_underlying(Edge::Start)]; } else if (edges[yoga::to_underlying(Edge::Right)].isDefined()) { - return pool_.getLength(edges[yoga::to_underlying(Edge::Right)]); + return edges[yoga::to_underlying(Edge::Right)]; } else if (edges[yoga::to_underlying(Edge::Horizontal)].isDefined()) { - return pool_.getLength(edges[yoga::to_underlying(Edge::Horizontal)]); + return edges[yoga::to_underlying(Edge::Horizontal)]; } else { - return pool_.getLength(edges[yoga::to_underlying(Edge::All)]); + return edges[yoga::to_underlying(Edge::All)]; } } - Style::Length computeBottomEdge(const Edges& edges) const { + StyleValueHandle computeBottomEdge(const Edges& edges) const { if (edges[yoga::to_underlying(Edge::Bottom)].isDefined()) { - return pool_.getLength(edges[yoga::to_underlying(Edge::Bottom)]); + return edges[yoga::to_underlying(Edge::Bottom)]; } else if (edges[yoga::to_underlying(Edge::Vertical)].isDefined()) { - return pool_.getLength(edges[yoga::to_underlying(Edge::Vertical)]); + return edges[yoga::to_underlying(Edge::Vertical)]; } else { - return pool_.getLength(edges[yoga::to_underlying(Edge::All)]); + return edges[yoga::to_underlying(Edge::All)]; } } - Style::Length computePosition(PhysicalEdge edge, Direction direction) const { + StyleValueHandle computePosition(PhysicalEdge edge, Direction direction) + const { switch (edge) { case PhysicalEdge::Left: return computeLeftEdge(position_, direction); @@ -796,7 +806,7 @@ class YG_EXPORT Style { } } - Style::Length computeMargin(PhysicalEdge edge, Direction direction) const { + StyleValueHandle computeMargin(PhysicalEdge edge, Direction direction) const { switch (edge) { case PhysicalEdge::Left: return computeLeftEdge(margin_, direction); @@ -811,7 +821,8 @@ class YG_EXPORT Style { } } - Style::Length computePadding(PhysicalEdge edge, Direction direction) const { + StyleValueHandle computePadding(PhysicalEdge edge, Direction direction) + const { switch (edge) { case PhysicalEdge::Left: return computeLeftEdge(padding_, direction); @@ -826,7 +837,7 @@ class YG_EXPORT Style { } } - Style::Length computeBorder(PhysicalEdge edge, Direction direction) const { + StyleValueHandle computeBorder(PhysicalEdge edge, Direction direction) const { switch (edge) { case PhysicalEdge::Left: return computeLeftEdge(border_, direction); @@ -841,6 +852,26 @@ class YG_EXPORT Style { } } + /** + * Internal resolution of a StyleValueHandle. + * + * Part of the handle-based optimization, this function allows the layout + * engine to resolve stored values (Points, Percents) directly from the pool + * via handles. This avoids the overhead of materializing an intermediate + * StyleLength/StyleSizeLength object on the stack during hot-path overhead + * calculations. + */ + FloatOptional resolve(StyleValueHandle handle, float referenceLength) const { + if (handle.isPoint()) { + return FloatOptional{pool_.getStoredValue(handle)}; + } + if (handle.isPercent()) { + return FloatOptional{ + pool_.getStoredValue(handle) * referenceLength * 0.01f}; + } + return FloatOptional{}; + } + Direction direction_ : bitCount() = Direction::Inherit; FlexDirection flexDirection_ : bitCount() = FlexDirection::Column; diff --git a/packages/react-native/ReactCommon/yoga/yoga/style/StyleValueHandle.h b/packages/react-native/ReactCommon/yoga/yoga/style/StyleValueHandle.h index d9c6ae05791e..6a2b58902339 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/style/StyleValueHandle.h +++ b/packages/react-native/ReactCommon/yoga/yoga/style/StyleValueHandle.h @@ -49,6 +49,14 @@ class StyleValueHandle { return type() == Type::Auto; } + constexpr bool isPercent() const { + return type() == Type::Percent; + } + + constexpr bool isPoint() const { + return type() == Type::Point; + } + private: friend class StyleValuePool; diff --git a/packages/react-native/ReactCommon/yoga/yoga/style/StyleValuePool.h b/packages/react-native/ReactCommon/yoga/yoga/style/StyleValuePool.h index dfee30ade9f1..fb31193b9789 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/style/StyleValuePool.h +++ b/packages/react-native/ReactCommon/yoga/yoga/style/StyleValuePool.h @@ -121,6 +121,16 @@ class StyleValuePool { } } + float getStoredValue(StyleValueHandle handle) const { + assert( + handle.type() == StyleValueHandle::Type::Point || + handle.type() == StyleValueHandle::Type::Percent || + handle.type() == StyleValueHandle::Type::Number); + return handle.isValueIndexed() + ? std::bit_cast(buffer_.get32(handle.value())) + : unpackInlineInteger(handle.value()); + } + private: void storeValue( StyleValueHandle& handle,