-
Notifications
You must be signed in to change notification settings - Fork 764
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Replace CompactValue with StyleValueHandle and StyleValuePool
Summary: X-link: facebook/react-native#42131 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). This does seem to make style reads slower, which due to their heavy frequency, does have a performance impact observable by synthetics. In an example laying out a tree of 10,000 nodes, we originally read from `StyleValuePool` 2.4 million times. This originally resulted in a ~10% regression, but when combined with the changes in the last diff, most style reads become simple bitwise operations on the handle, and we are actually 14% faster than before. | | 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% | | "Huge nested layout" microbenchmark (M1 Ultra) | 11.5ms | 9.9ms | -1.6ms/-14% | | Quest Store C++ heap usage (avg over 10 runs) | 86.2MB | 84.9MB | -1.3MB/-1.5% | Reviewed By: joevilches Differential Revision: D52223122 fbshipit-source-id: 990f4b7e991e8e22d198ce20f7da66d9c6ba637b
- Loading branch information
1 parent
080ef9c
commit 223a9f6
Showing
7 changed files
with
478 additions
and
260 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
This file was deleted.
Oops, something went wrong.
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
/* | ||
* 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 <cassert> | ||
#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_++; | ||
assert(index < 4096 && "SmallValueBuffer can only hold up to 4096 chunks"); | ||
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); | ||
[[maybe_unused]] const auto msbIndex = push(msb); | ||
assert( | ||
msbIndex < 4096 && "SmallValueBuffer can only hold up to 4096 chunks"); | ||
|
||
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 |
Oops, something went wrong.