Skip to content

Commit

Permalink
Replace CompactValue with StyleValueHandle and StyleValuePool (facebo…
Browse files Browse the repository at this point in the history
…ok#42131)

Summary:

X-link: facebook/yoga#1534

Now that the storage method is a hidden implementation detail, this changes the underlying data structure used to store styles, from `CompactValue` (a customized 32-bit float with tag bits), to `StyleValuePool`.

This new structure operates on 16-bit handles, and a shared small buffer. The vast majority of real-world values can be stored directly in the handle, but we allow arbitrary 32 bit (and soon 64-bit) values to be stored, where the handle then becomes an index into the styles buffer.

This results in a real-world memory usage win, while also letting us store the 64-bit values we are wanting to use for math function support (compared to doubling the storage requirements).

| | Before | After | Δ |
| `sizeof(yoga::Style)` | 208B  | 144B | -64B/-31% |
| `sizeof(yoga::Node)` | 640B  | 576B | -64B/-10% |
| `sizeof(YogaLayoutableShadowNode) ` |  920B | 856B | -64B/-7% |
| `sizeof(YogaLayoutableShadowNode) + sizeof(YogaStylableProps)` | 1296B  | 1168B | -128B/-10% |
| `sizeof(ViewShadowNode)`  |  920B | 856B | -64B/-7% |
| `sizeof(ViewShadowNode) + sizeof(ViewShadowNodeProps)` | 2000B  | 1872B | -128B/-6% |

Differential Revision: D52223122
  • Loading branch information
NickGerleman authored and facebook-github-bot committed Jan 4, 2024
1 parent 73d02fa commit 677f31b
Show file tree
Hide file tree
Showing 7 changed files with 454 additions and 244 deletions.
16 changes: 7 additions & 9 deletions packages/react-native/ReactCommon/yoga/yoga/YGNodeStyle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,13 @@ void updateStyle(YGNodeRef node, IdxT idx, ValueT value) {

} // namespace

void YGNodeCopyStyle(
const YGNodeRef dstNodeRef,
const YGNodeConstRef srcNodeRef) {
auto dstNode = resolveRef(dstNodeRef);
auto srcNode = resolveRef(srcNodeRef);

if (!(dstNode->getStyle() == srcNode->getStyle())) {
dstNode->setStyle(srcNode->getStyle());
dstNode->markDirtyAndPropagate();
void YGNodeCopyStyle(YGNodeRef dstNode, YGNodeConstRef srcNode) {
auto dst = resolveRef(dstNode);
auto src = resolveRef(srcNode);

if (dst->getStyle() != src->getStyle()) {
dst->setStyle(src->getStyle());
dst->markDirtyAndPropagate();
}
}

Expand Down
8 changes: 4 additions & 4 deletions packages/react-native/ReactCommon/yoga/yoga/node/Node.h
Original file line number Diff line number Diff line change
Expand Up @@ -375,13 +375,13 @@ class YG_EXPORT Node : public ::YGNode {
YGBaselineFunc baselineFunc_ = {nullptr};
YGPrintFunc printFunc_ = {nullptr};
YGDirtiedFunc dirtiedFunc_ = nullptr;
Style style_ = {};
LayoutResults layout_ = {};
Style style_;
LayoutResults layout_;
size_t lineIndex_ = 0;
Node* owner_ = nullptr;
std::vector<Node*> children_ = {};
std::vector<Node*> children_;
const Config* config_;
std::array<Style::Length, 2> resolvedDimensions_ = {
std::array<Style::Length, 2> resolvedDimensions_{
{value::undefined(), value::undefined()}};
};

Expand Down
177 changes: 0 additions & 177 deletions packages/react-native/ReactCommon/yoga/yoga/style/CompactValue.h

This file was deleted.

129 changes: 129 additions & 0 deletions packages/react-native/ReactCommon/yoga/yoga/style/SmallValueBuffer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#pragma once

#include <array>
#include <bitset>
#include <cstdint>
#include <memory>
#include <vector>

namespace facebook::yoga {

// Container which allows storing 32 or 64 bit integer values, whose index may
// never change. Values are first stored in a fixed buffer of `BufferSize`
// 32-bit chunks, before falling back to heap allocation.
template <size_t BufferSize>
class SmallValueBuffer {
public:
SmallValueBuffer() = default;
SmallValueBuffer(const SmallValueBuffer& other) {
*this = other;
}
SmallValueBuffer(SmallValueBuffer&& other) = default;

// Add a new element to the buffer, returning the index of the element
uint16_t push(uint32_t value) {
const auto index = count_++;
if (index < buffer_.size()) {
buffer_[index] = value;
return index;
}

if (overflow_ == nullptr) {
overflow_ = std::make_unique<SmallValueBuffer::Overflow>();
}

overflow_->buffer_.push_back(value);
overflow_->wideElements_.push_back(false);
return index;
}

uint16_t push(uint64_t value) {
const auto lsb = static_cast<uint32_t>(value & 0xFFFFFFFF);
const auto msb = static_cast<uint32_t>(value >> 32);

const auto lsbIndex = push(lsb);
push(msb);

if (lsbIndex < buffer_.size()) {
wideElements_[lsbIndex] = true;
} else {
overflow_->wideElements_[lsbIndex - buffer_.size()] = true;
}
return lsbIndex;
}

// Replace an existing element in the buffer with a new value. A new index
// may be returned, e.g. if a new value is wider than the previous.
[[nodiscard]] uint16_t replace(uint16_t index, uint32_t value) {
if (index < buffer_.size()) {
buffer_[index] = value;
} else {
overflow_->buffer_.at(index - buffer_.size()) = value;
}

return index;
}

[[nodiscard]] uint16_t replace(uint16_t index, uint64_t value) {
const bool isWide = index < wideElements_.size()
? wideElements_[index]
: overflow_->wideElements_.at(index - buffer_.size());

if (isWide) {
const auto lsb = static_cast<uint32_t>(value & 0xFFFFFFFF);
const auto msb = static_cast<uint32_t>(value >> 32);

[[maybe_unused]] auto lsbIndex = replace(index, lsb);
[[maybe_unused]] auto msbIndex = replace(index + 1, msb);
return index;
} else {
return push(value);
}
}

// Get a value of a given width
uint32_t get32(uint16_t index) const {
if (index < buffer_.size()) {
return buffer_[index];
} else {
return overflow_->buffer_.at(index - buffer_.size());
}
}

uint64_t get64(uint16_t index) const {
const auto lsb = get32(index);
const auto msb = get32(index + 1);
return (static_cast<uint64_t>(msb) << 32) | lsb;
}

SmallValueBuffer& operator=(const SmallValueBuffer& other) {
count_ = other.count_;
buffer_ = other.buffer_;
wideElements_ = other.wideElements_;
overflow_ = other.overflow_ ? std::make_unique<Overflow>(*other.overflow_)
: nullptr;
return *this;
}

SmallValueBuffer& operator=(SmallValueBuffer&& other) = default;

private:
struct Overflow {
std::vector<uint32_t> buffer_;
std::vector<bool> wideElements_;
};

uint16_t count_{0};
std::array<uint32_t, BufferSize> buffer_;
std::bitset<BufferSize> wideElements_;
std::unique_ptr<Overflow> overflow_;
};

} // namespace facebook::yoga
Loading

0 comments on commit 677f31b

Please sign in to comment.