Minor display: contents optimizations (#1736)

Summary:
Pull Request resolved: https://github.com/facebook/yoga/pull/1736

X-link: https://github.com/facebook/react-native/pull/47358

`LayoutableChildren<yoga::Node>::Iterator` showed up to a surprising extent on a recent trace. Part of this was during pixel grid rounding, which does full tree traversal (we should fix that...), where the iterator is the first thing to read from the node.

I ran Yoga microbenchmark with Yoga compiled with `-O2`, where we saw a regression of synthetic performance by ~10%, but it turns out this build also had ASAN and some other heavy bits enabled, so the real impact was quite lower (~6%).

I was able to make some optimizations in the meantime against that, which still show some minor wins, reducing that overhead to ~4% in the properly optimized build (and a bit more before that). This is still measurable on the beefy server, and the code is a bit cleaner, so let's commit these!

Note that, in real scenarios, measure functions may dominate layout time, so display: contents does not mean end-to-end 4% regression, even after this change.

This change makes a few different optimizations
1. Removes redundant copies
2. Removes redundant index keeping
3. Mark which branches are likely vs unlikely
4. Shrink iterator size from 6 pointers to 3 pointers
5. Avoid usage in pixel grid rounding (so we don't need to have cache read for style)

In "Huge nested layout" example

| Before display: contents support | After display: contents support | After optimizations |
| 9.77ms | 10.39ms | 10.17ms |

Changelog: [Internal]

Reviewed By: rozele

Differential Revision: D65336148

fbshipit-source-id: 01c592771ed7accf2d87dddd5a3a9e0225098b56
This commit is contained in:
Nick Gerleman
2024-11-05 11:28:37 -08:00
committed by Facebook GitHub Bot
parent 867bfae3a3
commit 8f69ac776e
5 changed files with 27 additions and 37 deletions

View File

@@ -540,7 +540,7 @@ static float computeFlexBasisForChildren(
const uint32_t generationCount) { const uint32_t generationCount) {
float totalOuterFlexBasis = 0.0f; float totalOuterFlexBasis = 0.0f;
YGNodeRef singleFlexChild = nullptr; YGNodeRef singleFlexChild = nullptr;
const auto& children = node->getLayoutChildren(); auto children = node->getLayoutChildren();
SizingMode sizingModeMainDim = SizingMode sizingModeMainDim =
isRow(mainAxis) ? widthSizingMode : heightSizingMode; isRow(mainAxis) ? widthSizingMode : heightSizingMode;
// If there is only one child with flexGrow + flexShrink it means we can set // If there is only one child with flexGrow + flexShrink it means we can set

View File

@@ -29,8 +29,7 @@ FlexLine calculateFlexLine(
float totalFlexGrowFactors = 0.0f; float totalFlexGrowFactors = 0.0f;
float totalFlexShrinkScaledFactors = 0.0f; float totalFlexShrinkScaledFactors = 0.0f;
size_t numberOfAutoMargins = 0; size_t numberOfAutoMargins = 0;
size_t endOfLineIndex = iterator.index(); yoga::Node* firstElementInLine = nullptr;
size_t firstElementInLineIndex = iterator.index();
float sizeConsumedIncludingMinConstraint = 0; float sizeConsumedIncludingMinConstraint = 0;
const Direction direction = node->resolveDirection(ownerDirection); const Direction direction = node->resolveDirection(ownerDirection);
@@ -40,19 +39,19 @@ FlexLine calculateFlexLine(
const float gap = const float gap =
node->style().computeGapForAxis(mainAxis, availableInnerMainDim); node->style().computeGapForAxis(mainAxis, availableInnerMainDim);
const auto childrenEnd = node->getLayoutChildren().end();
// Add items to the current line until it's full or we run out of items. // Add items to the current line until it's full or we run out of items.
for (; iterator != node->getLayoutChildren().end(); for (; iterator != childrenEnd; iterator++) {
iterator++, endOfLineIndex = iterator.index()) {
auto child = *iterator; auto child = *iterator;
if (child->style().display() == Display::None || if (child->style().display() == Display::None ||
child->style().positionType() == PositionType::Absolute) { child->style().positionType() == PositionType::Absolute) {
if (firstElementInLineIndex == endOfLineIndex) {
// We haven't found the first contributing element in the line yet.
firstElementInLineIndex++;
}
continue; continue;
} }
if (firstElementInLine == nullptr) {
firstElementInLine = child;
}
if (child->style().flexStartMarginIsAuto(mainAxis, ownerDirection)) { if (child->style().flexStartMarginIsAuto(mainAxis, ownerDirection)) {
numberOfAutoMargins++; numberOfAutoMargins++;
} }
@@ -60,13 +59,11 @@ FlexLine calculateFlexLine(
numberOfAutoMargins++; numberOfAutoMargins++;
} }
const bool isFirstElementInLine =
(endOfLineIndex - firstElementInLineIndex) == 0;
child->setLineIndex(lineCount); child->setLineIndex(lineCount);
const float childMarginMainAxis = const float childMarginMainAxis =
child->style().computeMarginForAxis(mainAxis, availableInnerWidth); child->style().computeMarginForAxis(mainAxis, availableInnerWidth);
const float childLeadingGapMainAxis = isFirstElementInLine ? 0.0f : gap; const float childLeadingGapMainAxis =
child == firstElementInLine ? 0.0f : gap;
const float flexBasisWithMinAndMaxConstraints = const float flexBasisWithMinAndMaxConstraints =
boundAxisWithinMinAndMax( boundAxisWithinMinAndMax(
child, child,
@@ -117,7 +114,6 @@ FlexLine calculateFlexLine(
return FlexLine{ return FlexLine{
.itemsInFlow = std::move(itemsInFlow), .itemsInFlow = std::move(itemsInFlow),
.sizeConsumed = sizeConsumed, .sizeConsumed = sizeConsumed,
.endOfLineIndex = endOfLineIndex,
.numberOfAutoMargins = numberOfAutoMargins, .numberOfAutoMargins = numberOfAutoMargins,
.layout = FlexLineRunningLayout{ .layout = FlexLineRunningLayout{
totalFlexGrowFactors, totalFlexGrowFactors,

View File

@@ -49,9 +49,6 @@ struct FlexLine {
// the flexible children. // the flexible children.
const float sizeConsumed{0.0f}; const float sizeConsumed{0.0f};
// The index of the first item beyond the current line.
const size_t endOfLineIndex{0};
// Number of edges along the line flow with an auto margin. // Number of edges along the line flow with an auto margin.
const size_t numberOfAutoMargins{0}; const size_t numberOfAutoMargins{0};

View File

@@ -124,7 +124,7 @@ void roundLayoutResultsToPixelGrid(
Dimension::Height); Dimension::Height);
} }
for (yoga::Node* child : node->getLayoutChildren()) { for (yoga::Node* child : node->getChildren()) {
roundLayoutResultsToPixelGrid(child, absoluteNodeLeft, absoluteNodeTop); roundLayoutResultsToPixelGrid(child, absoluteNodeLeft, absoluteNodeTop);
} }
} }

View File

@@ -4,10 +4,12 @@
* This source code is licensed under the MIT license found in the * This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <vector> #include <forward_list>
#include <utility>
#include <yoga/enums/Display.h> #include <yoga/enums/Display.h>
@@ -18,7 +20,6 @@ class Node;
template <typename T> template <typename T>
class LayoutableChildren { class LayoutableChildren {
public: public:
using Backtrack = std::vector<std::pair<const T*, size_t>>;
struct Iterator { struct Iterator {
using iterator_category = std::input_iterator_tag; using iterator_category = std::input_iterator_tag;
using difference_type = std::ptrdiff_t; using difference_type = std::ptrdiff_t;
@@ -30,10 +31,6 @@ class LayoutableChildren {
Iterator(const T* node, size_t childIndex) Iterator(const T* node, size_t childIndex)
: node_(node), childIndex_(childIndex) {} : node_(node), childIndex_(childIndex) {}
Iterator(const T* node, size_t childIndex, Backtrack&& backtrack)
: node_(node),
childIndex_(childIndex),
backtrack_(std::move(backtrack)) {}
T* operator*() const { T* operator*() const {
return node_->getChild(childIndex_); return node_->getChild(childIndex_);
@@ -41,7 +38,6 @@ class LayoutableChildren {
Iterator& operator++() { Iterator& operator++() {
next(); next();
currentNodeIndex_++;
return *this; return *this;
} }
@@ -51,10 +47,6 @@ class LayoutableChildren {
return tmp; return tmp;
} }
size_t index() const {
return currentNodeIndex_;
}
friend bool operator==(const Iterator& a, const Iterator& b) { friend bool operator==(const Iterator& a, const Iterator& b) {
return a.node_ == b.node_ && a.childIndex_ == b.childIndex_; return a.node_ == b.node_ && a.childIndex_ == b.childIndex_;
} }
@@ -68,16 +60,16 @@ class LayoutableChildren {
if (childIndex_ + 1 >= node_->getChildCount()) { if (childIndex_ + 1 >= node_->getChildCount()) {
// if the current node has no more children, try to backtrack and // if the current node has no more children, try to backtrack and
// visit its successor // visit its successor
if (backtrack_.empty()) { if (backtrack_.empty()) [[likely]] {
// if there are no nodes to backtrack to, the last node has been // if there are no nodes to backtrack to, the last node has been
// visited // visited
*this = Iterator{}; *this = Iterator{};
} else { } else {
// pop and restore the latest backtrack entry // pop and restore the latest backtrack entry
const auto back = backtrack_.back(); const auto& back = backtrack_.front();
backtrack_.pop_back();
node_ = back.first; node_ = back.first;
childIndex_ = back.second; childIndex_ = back.second;
backtrack_.pop_front();
// go to the next node // go to the next node
next(); next();
@@ -87,7 +79,10 @@ class LayoutableChildren {
++childIndex_; ++childIndex_;
// skip all display: contents nodes, possibly going deeper into the // skip all display: contents nodes, possibly going deeper into the
// tree // tree
skipContentsNodes(); if (node_->getChild(childIndex_)->style().display() ==
Display::Contents) [[unlikely]] {
skipContentsNodes();
}
} }
} }
@@ -99,7 +94,7 @@ class LayoutableChildren {
// if it has display: contents set, it shouldn't be returned but its // if it has display: contents set, it shouldn't be returned but its
// children should in its place push the current node and child index // children should in its place push the current node and child index
// so that the current state can be restored when backtracking // so that the current state can be restored when backtracking
backtrack_.push_back({node_, childIndex_}); backtrack_.push_front({node_, childIndex_});
// traverse the child // traverse the child
node_ = currentNode; node_ = currentNode;
childIndex_ = 0; childIndex_ = 0;
@@ -117,8 +112,7 @@ class LayoutableChildren {
const T* node_{nullptr}; const T* node_{nullptr};
size_t childIndex_{0}; size_t childIndex_{0};
size_t currentNodeIndex_{0}; std::forward_list<std::pair<const T*, size_t>> backtrack_;
Backtrack backtrack_;
friend LayoutableChildren; friend LayoutableChildren;
}; };
@@ -133,7 +127,10 @@ class LayoutableChildren {
Iterator begin() const { Iterator begin() const {
if (node_->getChildCount() > 0) { if (node_->getChildCount() > 0) {
auto result = Iterator(node_, 0); auto result = Iterator(node_, 0);
result.skipContentsNodes(); if (node_->getChild(0)->style().display() == Display::Contents)
[[unlikely]] {
result.skipContentsNodes();
}
return result; return result;
} else { } else {
return Iterator{}; return Iterator{};