Files
yoga/tests/SmallValueBufferTest.cpp
Nick Gerleman 67154d47a3 Replace CompactValue with StyleValueHandle and StyleValuePool (#1534)
Summary:
X-link: https://github.com/facebook/react-native/pull/42131

Pull Request resolved: https://github.com/facebook/yoga/pull/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
2024-01-19 18:22:29 -08:00

162 lines
4.5 KiB
C++

/*
* 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.
*/
#include <gtest/gtest.h>
#include <yoga/style/SmallValueBuffer.h>
namespace facebook::yoga {
constexpr size_t kBufferSize = 4;
TEST(SmallValueBuffer, copy_assignment_with_overflow) {
std::array<uint16_t, kBufferSize + 1> handles;
SmallValueBuffer<kBufferSize> buffer1;
for (size_t i = 0; i < kBufferSize + 1; ++i) {
handles[i] = buffer1.push(static_cast<uint32_t>(i));
}
SmallValueBuffer<kBufferSize> buffer2 = buffer1;
for (size_t i = 0; i < kBufferSize + 1; ++i) {
EXPECT_EQ(buffer2.get32(handles[i]), i);
}
auto handle = buffer1.push(42u);
EXPECT_EQ(buffer1.get32(handle), 42);
EXPECT_THROW({ buffer2.get32(handle); }, std::logic_error);
}
TEST(SmallValueBuffer, push_32) {
uint32_t magic = 88567114u;
SmallValueBuffer<kBufferSize> buffer;
auto handle = buffer.push(magic);
EXPECT_EQ(buffer.get32(handle), magic);
}
TEST(SmallValueBuffer, push_overflow) {
uint32_t magic1 = 88567114u;
uint32_t magic2 = 351012214u;
uint32_t magic3 = 146122128u;
uint32_t magic4 = 2171092154u;
uint32_t magic5 = 2269016953u;
SmallValueBuffer<kBufferSize> buffer;
EXPECT_EQ(buffer.get32(buffer.push(magic1)), magic1);
EXPECT_EQ(buffer.get32(buffer.push(magic2)), magic2);
EXPECT_EQ(buffer.get32(buffer.push(magic3)), magic3);
EXPECT_EQ(buffer.get32(buffer.push(magic4)), magic4);
EXPECT_EQ(buffer.get32(buffer.push(magic5)), magic5);
}
TEST(SmallValueBuffer, push_64) {
uint64_t magic = 118138934255546108ull;
SmallValueBuffer<kBufferSize> buffer;
auto handle = buffer.push(magic);
EXPECT_EQ(buffer.get64(handle), magic);
}
TEST(SmallValueBuffer, push_64_overflow) {
uint64_t magic1 = 1401612388342512ull;
uint64_t magic2 = 118712305386210ull;
uint64_t magic3 = 752431801563359011ull;
uint64_t magic4 = 118138934255546108ull;
uint64_t magic5 = 237115443124116111ull;
SmallValueBuffer<kBufferSize> buffer;
EXPECT_EQ(buffer.get64(buffer.push(magic1)), magic1);
EXPECT_EQ(buffer.get64(buffer.push(magic2)), magic2);
EXPECT_EQ(buffer.get64(buffer.push(magic3)), magic3);
EXPECT_EQ(buffer.get64(buffer.push(magic4)), magic4);
EXPECT_EQ(buffer.get64(buffer.push(magic5)), magic5);
}
TEST(SmallValueBuffer, push_64_after_32) {
uint32_t magic32 = 88567114u;
uint64_t magic64 = 118712305386210ull;
SmallValueBuffer<kBufferSize> buffer;
auto handle32 = buffer.push(magic32);
EXPECT_EQ(buffer.get32(handle32), magic32);
auto handle64 = buffer.push(magic64);
EXPECT_EQ(buffer.get64(handle64), magic64);
}
TEST(SmallValueBuffer, push_32_after_64) {
uint32_t magic32 = 88567114u;
uint64_t magic64 = 118712305386210ull;
SmallValueBuffer<kBufferSize> buffer;
auto handle64 = buffer.push(magic64);
EXPECT_EQ(buffer.get64(handle64), magic64);
auto handle32 = buffer.push(magic32);
EXPECT_EQ(buffer.get32(handle32), magic32);
}
TEST(SmallValueBuffer, replace_32_with_32) {
uint32_t magic1 = 88567114u;
uint32_t magic2 = 351012214u;
SmallValueBuffer<kBufferSize> buffer;
auto handle = buffer.push(magic1);
EXPECT_EQ(buffer.get32(buffer.replace(handle, magic2)), magic2);
}
TEST(SmallValueBuffer, replace_32_with_64) {
uint32_t magic32 = 88567114u;
uint64_t magic64 = 118712305386210ull;
SmallValueBuffer<kBufferSize> buffer;
auto handle = buffer.push(magic32);
EXPECT_EQ(buffer.get64(buffer.replace(handle, magic64)), magic64);
}
TEST(SmallValueBuffer, replace_32_with_64_causes_overflow) {
uint32_t magic1 = 88567114u;
uint32_t magic2 = 351012214u;
uint32_t magic3 = 146122128u;
uint32_t magic4 = 2171092154u;
uint64_t magic64 = 118712305386210ull;
SmallValueBuffer<kBufferSize> buffer;
auto handle1 = buffer.push(magic1);
buffer.push(magic2);
buffer.push(magic3);
buffer.push(magic4);
EXPECT_EQ(buffer.get64(buffer.replace(handle1, magic64)), magic64);
}
TEST(SmallValueBuffer, replace_64_with_32) {
uint32_t magic32 = 88567114u;
uint64_t magic64 = 118712305386210ull;
SmallValueBuffer<kBufferSize> buffer;
auto handle = buffer.push(magic64);
EXPECT_EQ(buffer.get32(buffer.replace(handle, magic32)), magic32);
}
TEST(SmallValueBuffer, replace_64_with_64) {
uint64_t magic1 = 1401612388342512ull;
uint64_t magic2 = 118712305386210ull;
SmallValueBuffer<kBufferSize> buffer;
auto handle = buffer.push(magic1);
EXPECT_EQ(buffer.get64(buffer.replace(handle, magic2)), magic2);
}
} // namespace facebook::yoga