Performance
Reactive V2 against V1 and BSML, in BeatLeader and in stress tests, on PC and Quest.
We measured BeatLeader's own UI on Reactive V1 and V2, then ran the same stress tests on V1, V2 and BSML on PC, and on Reactive and BSML on Quest 3.
- BeatLeader: UI time is unchanged, and V1's memory leak is gone.
- V2 vs V1: V2 mounts about 4× faster. Its layout lead comes from a faster Yoga build, not from Reactive. State is far faster in the worst cases, while single writes are slower.
- Reactive vs BSML: Reactive is much faster when part of a big UI changes, and usually mounts faster. BSML is faster when many labels change text, and when moving things without layout.
Each bar shows how many times better one side is, on a log scale. No bar means the difference is within noise. A * marks a row where the two sides don't do the same work or weren't measured the same way, with a note under its group. Don't compare PC numbers with Quest numbers.
BeatLeader
V1 never frees the Yoga nodes of destroyed Images, Labels and list rows, or anything they reference, so its memory grows for as long as the game runs.
V2 vs V1
* V1 sets each label's text directly. V2 writes 300 bound states, the way BeatLeader's V2 UI does.
* Not the same work: V1 calls back on every write, 100000 times per frame in the burst and 262144 times at the end of the diamonds. V2 runs each callback once per flush, with the latest value.
** V1 has no subscription handle, so this uses its C# event, which copies the whole list of callbacks on every add and remove.
* V1 ran each of these in a fresh game session, because its leak slows down everything after it, while V2 ran the full suite. So this is V1's best case.
* Also from a fresh V1 session each.
* Also from a fresh V1 session.
Single writes, chains of maps and states writing each other are slower because V2 compares the value, queues the state and notifies on flush. That's also what makes bursts cheap, and they all stay under a millisecond.
Is the layout gain Reactive or Yoga?
V1 ships with an unoptimized yoga.dll, and V2 was measured on a Release build. With both on the Release build:
* V1 ran each of these in a fresh game session, V2 in the full suite.
V1 catches up on layout and is up to 1.6× faster in some cases, but its mounts stay slow. So the layout gain comes from the Yoga build, and the mount gain from Reactive.
Reactive vs BSML
BSML can't always do the same work, see where the work differs.
PC
* BSML uses 40 rows of 25, because its layout groups can't wrap items of different sizes. Reactive wraps all 1000 in one container.
** BSML uses 30 rows of 10 and sets each text directly. Reactive wraps all 300 in one container and writes 300 bound states.
* BSML's labels sit in grid cells of a fixed size, so their text isn't measured for layout, while Reactive's are sized by their text.
* BSML has no animations, so a hand-written loop works out one value per frame and sets it on every item. Reactive runs a separate animation per item.
** The same loop, and BSML's items ignore layout, so moving them only writes their transforms. Reactive moves absolute flex items, so its layout runs every frame.
* BSML's custom-list creates and parses a new cell whenever a row comes into view, instead of reusing one. That's BSML's own behavior.
** BSML has no list to give new items to, so each row's bound object is updated in place. Reactive's list gets a new array of items.
* BSML parses its markup on every mount, where Reactive builds from code. BSML also places the first three in a grid of fixed cells rather than wrapping them, so its 1000 Labels aren't measured for layout.
* BSML has no animations, so its 5000 Images hold no animation state, while Reactive's hold one each.
Quest 3
* BSML uses 40 rows of 25, because its layout groups can't wrap items of different sizes. Reactive wraps all 1000 in one container.
** BSML uses 30 rows of 10 and sets each text directly. Reactive wraps all 300 in one container and writes 300 bound states.
* Quest BSML has no change notifications, so the loop sets all 500 labels by hand. Its labels also sit in grid cells of a fixed size, so their text isn't measured for layout, while Reactive's are sized by their text.
* BSML has no animations, so a hand-written loop works out one value per frame and sets it on every item. Reactive runs a separate animation per item.
** The same loop, and BSML's items ignore layout, so moving them only writes their transforms. Reactive moves absolute flex items, so its layout runs every frame.
* Quest BSML has no change notifications, so the loop sets all 1000 labels by hand. Reactive's list gets a new array of items.
* BSML parses its markup on every mount, where Reactive builds from code. BSML also places the first three in a grid of fixed cells rather than wrapping them, so its 1000 Labels aren't measured for layout.
* BSML has no animations, so its 5000 Images hold no animation state, while Reactive's hold one each.
reactive-quest has no Table, so the table row is missing here.
How it was measured
Measured on 2026-11-05 with the benchmarks in Tests/UIHarness of the BeatLeader repository. Its README has the commands.
UI time is main thread time per frame over idle, as a p50. State benchmarks are timed directly. Mounts are the median mount after the first, including its first 6 frames. Memory is the managed heap with the benchmark mounted, plus the native heap on Quest.
Setup and caveats
- UI time window: from the first
Updateto the end ofCanvas.willRenderCanvases. - Same: the ranges over all rounds overlap, or the gap is under 5% or under the noise floor of an empty benchmark, which is 0.66 ms on V1 and V2, 1.95 ms on BSML and 0.03 ms on Quest.
- PC: Beat Saber 1.44.0, Ryzen 7 5800H, RTX 3070 Laptop, in FPFC on a hidden desktop, which makes absolute numbers higher than usual. V1 is Reactive
859fe5d, V2 is Reactive56b3941with a Releaseyoga.dll, BSML is 1.14.1. 3 rounds of V2 and BSML, 2 of V1. BeatLeader ran 2 rounds per version, and its main menu 5 sessions. - Quest: Beat Saber 1.40.8 on Quest 3 at 120 Hz, with no thermal throttling.
reactive-quest743aba6and BSML 0.4.55, 3 rounds each. - Same work: V1, V2 and Quest Reactive end with matching layouts, apart from animations, tables and
state-subscribe. BSML can't match them, so we compared screenshots of every benchmark instead. - Not covered: PC VR, other machines and GPU cost.
Where the work differs
Each target is written the way its library is normally used: V2 the way BeatLeader's UI is, V1 with its own API, and BSML with markup and ~value bindings. Where that changes the work, or a target was measured differently, the chart row has a * and a note under its group.
Every difference
V1 and V2
- Fresh sessions for V1: V1's leak slows down everything after it, so its mounts, flat lists,
life-churnandram-rowseach ran in a game session of their own, while V2 ran the full suite. That's V1's best case. V1's mounts on V2's Yoga also come from one session each, and its layout there from 2 rounds. - Callbacks: V1 calls back on every write. That's 100000 calls per frame in
state-burstand 262144 at the end ofstate-diamond-deep, where V2 runs each callback once per flush. So these rows show V2 doing less work by design. - Subscribing: V1 has no subscription handle, so
state-subscribeuses its C# event, which copies the whole list of callbacks on every add and remove. - Text: V1 sets label text directly, and V2 writes bound states, so V2's text benchmarks include its state work.
BSML
- No wrapping: UGUI's layout groups can't wrap items of different sizes, so where one item changes size, BSML uses rows of a fixed length with the same number of items. Elsewhere a
gridof fixed cells stands in for wrapping, so BSML's labels inlife-labelandstate-labelsaren't measured for layout, while Reactive's are. This helps BSML. - No animations: a hand-written loop works out one value per frame and sets it on every item, while Reactive runs one animation per item. That's about the least work a BSML view could do, not a library feature. In
anim-position, BSML's items also ignore layout, so only their transforms move, while Reactive's absolute flex items run layout every frame. Inram-animated, BSML's Images hold no animation state. All of this helps BSML. - Markup: BSML parses markup on every mount, including all 50 rows per frame in
life-churn. That's how BSML builds UI, so it's counted. - Tables on PC:
CustomCellListTableData.CellForIdxcreates a GameObject and parses the cell again whenever a row comes into view, instead of reusing one. That's BSML's own behavior, and Quest BSML reuses its cells. - No list component: in
list-flat-shuffle, each row's bound object is updated in place, while Reactive's list gets a new array of items. - No change notifications on Quest:
state-labelsandlist-flat-shuffleset the labels by hand there, which skips binding entirely. This helps BSML. - No state system:
state-labelsis the only state benchmark with a BSML version, andram-stateis left out.
All numbers
Show all numbers
UI time, p50. Milliseconds per frame over idle. State benchmarks are timed directly.
| Benchmark | Case | V1 | V2 | BSML | Quest Reactive | Quest BSML |
|---|---|---|---|---|---|---|
layout-rectNo layout engine: 1000 anchored Images, parent resized every frame | best | 16.9 | 18.8 | 17.5 | 29.1 | 28.6 |
layout-idle1000 flex items mounted, nothing changes | best | 1.86 | 1.69 | ≈0 | 0.19 | 0.04 |
layout-panels40 fixed-size panels of 25 items: one item changes size inside one panel every frame | best | 4.58 | 2.81 | 15.1 | 0.37 | 35.9 |
layout-leafFixed-size flex: one of 1000 items changes size every frame | average | 13.3 | 3.67 | 12.5 | 0.77 | 29.5 |
layout-rootFixed-size flex: 1000 items, the root resized every frame | worst | 13.4 | 3.78 | 3.99 | 2.54 | 7.58 |
layout-tree-fixedThe same tree with fixed-size leaves, so nothing is measured, the root resized every frame | worst | 20.3 | 5.81 | 34.9 | 3.63 | 73.0 |
layout-percentPercent-sized and growing flex: 500 items, the root resized every frame | average | 18.4 | 11.5 | 13.7 | 17.3 | 28.0 |
layout-treeTree of nested growing flex containers (1365 nodes), the root resized every frame | worst | 44.8 | 25.0 | 35.7 | 38.0 | 74.4 |
layout-text-oneText-measured flex: one of 300 labels changes its text every frame | average | 3.23 | 1.47 | 4.47 | 0.48 | 8.78 |
layout-rowsColumn of 200 list rows (800 nodes), the root resized every frame | average | 29.5 | 20.0 | 15.3 | 21.9 | 30.2 |
layout-text-allText-measured flex: all 300 labels change their text every frame | worst | 42.2 | 35.1 | 20.1 | 27.9 | 24.6 |
layout-tree-textTree of content-sized containers with text leaves (364 nodes): text and root change every frame | worst | 57.0 | 19.2 | 15.4 | 19.8 | 23.8 |
layout-deep128 nested flex containers, the innermost item changes size every frame | worst | 19.7 | 2.64 | 5.93 | 1.91 | 12.7 |
layout-absolute500 absolutely positioned flex items all move every frame | average | 5.22 | 3.46 | ≈0 | 0.71 | ≈0 |
layout-toggleHalf of 200 list rows hide and show every frame | worst | 17.7 | 14.7 | 19.1 | 8.90 | 22.3 |
state-labelsOne write per frame updates the text of 500 bound labels through a map | worst | 70.6 | 54.9 | 19.9 | 44.7 | 20.7 |
state-singleOne state, one callback, one write per frame | best | 2.0 µs | 7.4 µs | – | 6.0 µs | – |
state-same-value1000 writes of the same value per frame, one callback | best | 11.9 µs | 8.8 µs | – | 3.1 µs | – |
state-fanoutOne write per frame reaches 1000 callbacks | average | 74.1 µs | 38.6 µs | – | 25.1 µs | – |
state-chainCallbacks in callbacks: a chain of 100 maps | average | 10.7 µs | 30.1 µs | – | 29.9 µs | – |
state-burst1000 writes per frame with 100 callbacks | worst | 730 µs | 29.3 µs | – | 14.3 µs | – |
state-reentrantCallbacks in callbacks: two states write each other 200 times per frame | worst | 17.6 µs | 53.2 µs | – | 41.3 µs | – |
state-cascadeCallbacks in callbacks: each callback writes four more states, six levels deep (5461 writes) | worst | 304 µs | 667 µs | – | 384 µs | – |
state-diamondCallbacks in callbacks: 14 stacked diamonds, where one write reaches the end by 16384 paths | worst | 1.25 ms | 22.8 µs | – | 20.8 µs | – |
state-diamond-deepCallbacks in callbacks: 18 stacked diamonds, where one write reaches the end by 262144 paths | worst | 20.0 ms | 26.7 µs | – | 24.7 µs | – |
state-subscribe1000 subscribes and unsubscribes per frame on a state with 1000 callbacks | worst | 5.78 ms | 82.2 µs | – | 4.06 ms | – |
anim-color-small100 Images animating their color | best | 0.69 | 1.19 | ≈0 | 2.64 | 2.34 |
anim-position500 absolute items animating their position | average | 5.28 | 3.15 | ≈0 | 1.50 | 0.09 |
anim-color-large2000 Images animating their color, so no layout | average | 33.9 | 31.9 | 30.5 | 55.3 | 52.6 |
anim-size200 flex items animating their size, so layout every frame | worst | 5.48 | 4.30 | 5.19 | 7.78 | 11.9 |
anim-retarget1000 color animations restarted every frame | worst | 17.7 | 16.3 | 15.8 | 28.5 | 25.9 |
anim-stormNested flex dashboard: 64 panels of 4 boxes animating size and color, plus a label changing text every frame | worst | 25.4 | 12.6 | 13.8 | 18.7 | 26.9 |
list-table-idleVirtualized table with 100000 items, nothing changes | best | 0.98 | ≈0 | ≈0 | – | 0.23 |
list-table-scrollVirtualized table with 100000 items, scrolled by a row every frame | average | 8.34 | 8.07 | 6.63 | – | 1.08 |
list-table-jumpVirtualized table with 100000 items, jumping far every frame so every cell rebinds | worst | 7.74 | 7.78 | 126 | – | 14.6 |
list-table-refreshVirtualized table with 10000 items, replaced by a filtered list every frame | worst | 9.74 | 8.44 | 119 | – | 13.8 |
list-flat-idleFlat list of 2000 rows (8000 nodes), nothing changes | average | 27.3 | 21.3 | 15.2 | 5.24 | 1.35 |
list-flat-shuffleFlat list of 500 rows, all shifted by one every frame so every row rebinds | worst | 275 | 107 | 89.8 | 87.7 | 126 |
list-flat-growFlat list growing by 10 rows every frame | worst | 91.7 | 14.4 | 37.3 | 24.4 | 49.6 |
life-layout-noflexMount and destroy 1000 empty Layouts in a parent without a layout controller | best | 1.27 | ≈0 | ≈0 | 0.25 | -0.09 |
life-layoutMount and destroy 1000 empty Layouts | best | 1.79 | ≈0 | ≈0 | 0.22 | -0.09 |
life-imageMount and destroy 1000 Images | average | 5.08 | 1.21 | ≈0 | 0.21 | -0.10 |
life-labelMount and destroy 1000 Labels | average | 1.80 | 1.97 | ≈0 | 0.51 | 0.31 |
life-rowMount and destroy 500 list rows (background, icon, two labels) | worst | 4.33 | 5.13 | 2.16 | 1.06 | 0.34 |
life-appendMount and destroy 4000 empty Layouts in one container, to show how appending scales against life-layout | worst | 4.56 | 3.16 | 6.28 | 0.88 | 0.05 |
life-churnCreate and destroy 50 rows every frame | worst | 124 | 31.0 | 121 | 112 | 129 |
ram-state100000 states with one callback each | best | 2.28 | -1.07 | – | -0.16 | – |
ram-animated5000 boxes with a color animation each | average | 11.0 | 9.81 | 3.55 | 1.01 | 0.36 |
ram-rows2000 list rows (8000 nodes) | worst | 24.2 | 19.6 | 11.4 | 4.36 | 1.24 |
UI time, p99.
| Benchmark | V1 | V2 | BSML | Quest Reactive | Quest BSML |
|---|---|---|---|---|---|
layout-rect | 29.6 | 30.0 | 29.7 | 31.5 | 30.3 |
layout-idle | 7.18 | 6.77 | 7.20 | 1.08 | 0.50 |
layout-panels | 14.8 | 10.2 | 29.3 | 0.90 | 38.6 |
layout-leaf | 21.4 | 8.86 | 36.7 | 1.18 | 32.4 |
layout-root | 24.7 | 11.4 | 14.1 | 3.57 | 9.02 |
layout-tree-fixed | 33.8 | 13.6 | 57.7 | 4.66 | 77.9 |
layout-percent | 29.4 | 19.8 | 26.7 | 19.5 | 30.4 |
layout-tree | 63.5 | 42.7 | 58.6 | 40.8 | 78.8 |
layout-text-one | 10.3 | 6.60 | 12.6 | 1.10 | 10.9 |
layout-rows | 45.4 | 33.4 | 28.4 | 24.7 | 32.4 |
layout-text-all | 60.5 | 54.2 | 33.5 | 29.9 | 27.4 |
layout-tree-text | 85.3 | 33.4 | 26.9 | 22.5 | 26.8 |
layout-deep | 29.9 | 7.53 | 11.6 | 3.19 | 14.1 |
layout-absolute | 12.4 | 10.8 | 5.77 | 1.36 | 0.48 |
layout-toggle | 46.1 | 33.5 | 42.7 | 31.8 | 50.1 |
state-labels | 101 | 88.4 | 34.0 | 64.3 | 26.9 |
state-single | 3.9 µs | 13.8 µs | – | 10.5 µs | – |
state-same-value | 20.0 µs | 15.1 µs | – | 7.2 µs | – |
state-fanout | 180 µs | 71.0 µs | – | 55.9 µs | – |
state-chain | 19.0 µs | 70.3 µs | – | 76.2 µs | – |
state-burst | 1.12 ms | 44.3 µs | – | 31.7 µs | – |
state-reentrant | 42.9 µs | 102 µs | – | 83.6 µs | – |
state-cascade | 606 µs | 1.21 ms | – | 533 µs | – |
state-diamond | 1.60 ms | 38.0 µs | – | 68.2 µs | – |
state-diamond-deep | 25.0 ms | 42.7 µs | – | 78.4 µs | – |
state-subscribe | 268 ms | 134 µs | – | 5.01 ms | – |
anim-color-small | 5.27 | 6.40 | 6.01 | 3.69 | 4.00 |
anim-position | 12.6 | 10.6 | 5.36 | 2.61 | 1.28 |
anim-color-large | 52.0 | 52.0 | 48.0 | 60.3 | 57.4 |
anim-size | 13.1 | 12.0 | 12.2 | 12.8 | 15.9 |
anim-retarget | 28.9 | 28.5 | 28.1 | 30.4 | 28.4 |
anim-storm | 37.9 | 24.8 | 24.8 | 26.3 | 29.3 |
list-table-idle | 5.65 | 4.43 | 5.19 | – | 0.62 |
list-table-scroll | 16.5 | 17.9 | 16.8 | – | 1.74 |
list-table-jump | 15.7 | 15.9 | 392 | – | 16.6 |
list-table-refresh | 15.2 | 17.9 | 411 | – | 15.9 |
list-flat-idle | 176 | 34.4 | 27.1 | 6.33 | 2.70 |
list-flat-shuffle | 468 | 149 | 143 | 93.2 | 131 |
list-flat-grow | 213 | 64.8 | 74.3 | 69.6 | 73.8 |
life-layout-noflex | 3.58 | 3.25 | 2.13 | 0.45 | 0.18 |
life-layout | 2.94 | 2.61 | 2.49 | 0.56 | 0.12 |
life-image | 6.81 | 3.35 | 4.00 | 0.44 | 0.11 |
life-label | 5.19 | 5.91 | 3.61 | 1.11 | 0.59 |
life-row | 8.62 | 9.51 | 6.97 | 1.64 | 0.71 |
life-append | 9.97 | 6.44 | 11.1 | 1.05 | 0.24 |
life-churn | 385 | 151 | 299 | 136 | 154 |
ram-state | 3.92 | 0.66 | – | 0.09 | – |
ram-animated | 12.3 | 14.8 | 6.40 | 1.44 | 0.49 |
ram-rows | 45.5 | 28.4 | 18.3 | 4.94 | 2.21 |
Mount, ms.
| Benchmark | V1 | V2 | BSML | Quest Reactive | Quest BSML |
|---|---|---|---|---|---|
layout-rect | 79.5 | 66.2 | 582 | 368 | 579 |
layout-idle | 765 | 70.0 | 551 | 172 | 279 |
layout-panels | 1453 | 92.2 | 765 | 186 | 451 |
layout-leaf | 987 | 98.7 | 600 | 178 | 363 |
layout-root | 718 | 74.2 | 705 | 176 | 267 |
layout-tree-fixed | 1414 | 138 | 745 | 249 | 496 |
layout-percent | 477 | 43.3 | 231 | 89.8 | 174 |
layout-tree | 1158 | 120 | 813 | 262 | 500 |
layout-text-one | 530 | 95.4 | 170 | 251 | 259 |
layout-rows | 820 | 130 | 663 | 434 | 538 |
layout-text-all | 705 | 83.4 | 181 | 242 | 247 |
layout-tree-text | 457 | 62.1 | 210 | 237 | 254 |
layout-deep | 35.3 | 14.8 | 432 | 21.2 | 1483 |
layout-absolute | 1126 | 36.8 | 241 | 90.9 | 175 |
layout-toggle | 1790 | 115 | 562 | 420 | 536 |
state-labels | 1303 | 118 | 237 | 439 | 330 |
state-single | 11.8 | 2.06 | – | 0.68 | – |
state-same-value | 3.28 | 3.72 | – | 0.43 | – |
state-fanout | 3.57 | 16.7 | – | 0.76 | – |
state-chain | 11.4 | 1.99 | – | 0.27 | – |
state-burst | 3.36 | 2.99 | – | 0.28 | – |
state-reentrant | 6.67 | 4.32 | – | 0.23 | – |
state-cascade | 8.00 | 5.92 | – | 2.79 | – |
state-diamond | 13.8 | 5.59 | – | 0.84 | – |
state-diamond-deep | 5.50 | 10.8 | – | 0.50 | – |
state-subscribe | 7.78 | 2.10 | – | 0.48 | – |
anim-color-small | 357 | 15.6 | 39.1 | 24.4 | 31.2 |
anim-position | 681 | 45.4 | 316 | 106 | 171 |
anim-color-large | 2882 | 167 | 1094 | 400 | 564 |
anim-size | 272 | 17.6 | 95.1 | 42.4 | 72.5 |
anim-retarget | 1323 | 88.4 | 554 | 202 | 282 |
anim-storm | 568 | 62.3 | 259 | 135 | 217 |
list-table-idle | 275 | 35.8 | 147 | – | 232 |
list-table-scroll | 258 | 30.4 | 130 | – | 218 |
list-table-jump | 206 | 26.2 | 120 | – | 203 |
list-table-refresh | 229 | 33.8 | 110 | – | 116 |
list-flat-idle | 4640 | 1823 | 6153 | 4867 | 5867 |
list-flat-shuffle | 952 | 407 | 1563 | 1110 | 1334 |
list-flat-grow | 17.9 | 0.17 | 4.39 | 0.57 | 0.89 |
life-layout-noflex | 28.7 | 26.0 | 527 | 57.9 | 122 |
life-layout | 40.9 | 25.8 | 651 | 61.7 | 234 |
life-image | 320 | 71.8 | 556 | 173 | 279 |
life-label | 865 | 224 | 623 | 808 | 716 |
life-row | 1104 | 291 | 1444 | 1033 | 1224 |
life-append | 254 | 102 | 2238 | 258 | 1046 |
life-churn | 7.93 | 5.80 | 9.01 | 0.55 | 1.73 |
ram-state | 180 | 23.5 | – | 50.9 | – |
ram-animated | 27266 | 441 | 2672 | 1006 | 1505 |
ram-rows | 3397 | 1412 | 5865 | 4341 | 5703 |
Memory while mounted, MB.
| Benchmark | V1 | V2 | BSML | Quest Reactive | Quest BSML |
|---|---|---|---|---|---|
layout-rect | 0.82 | 0.21 | 3.70 | 0.94 | 1.11 |
layout-idle | 1.93 | 0.48 | 3.79 | 1.19 | 0.40 |
layout-panels | 1.76 | 0.20 | 4.07 | 1.66 | 0.99 |
layout-leaf | 1.95 | 0.46 | 3.79 | 1.62 | 0.81 |
layout-root | 1.90 | 0.41 | 3.70 | 1.61 | 0.76 |
layout-tree-fixed | 2.57 | 0.54 | 4.19 | 2.10 | 1.23 |
layout-percent | 0.50 | ≈0 | 0.72 | 0.77 | 0.39 |
layout-tree | 2.72 | 0.55 | 4.23 | 2.11 | 1.22 |
layout-text-one | 7.29 | 6.14 | 8.54 | ≈0 | 7.70 |
layout-rows | 8.02 | 4.95 | 12.7 | 7.78 | 7.05 |
layout-text-all | 7.41 | 6.06 | 8.54 | 7.98 | 7.71 |
layout-tree-text | 3.38 | 1.85 | 4.38 | 3.51 | 3.26 |
layout-deep | ≈0 | ≈0 | ≈0 | 0.15 | 0.16 |
layout-absolute | 0.16 | ≈0 | 0.52 | ≈0 | 0.60 |
layout-toggle | 7.90 | 4.97 | 12.8 | 7.74 | 7.07 |
state-labels | 8.01 | 10.5 | 10.2 | 13.5 | 5.85 |
state-single | ≈0 | ≈0 | – | ≈0 | – |
state-same-value | ≈0 | ≈0 | – | ≈0 | – |
state-fanout | ≈0 | ≈0 | – | 0.19 | – |
state-chain | ≈0 | ≈0 | – | ≈0 | – |
state-burst | ≈0 | ≈0 | – | ≈0 | – |
state-reentrant | ≈0 | ≈0 | – | ≈0 | – |
state-cascade | ≈0 | ≈0 | – | 1.01 | – |
state-diamond | ≈0 | ≈0 | – | ≈0 | – |
state-diamond-deep | ≈0 | ≈0 | – | ≈0 | – |
state-subscribe | ≈0 | ≈0 | – | 0.19 | – |
anim-color-small | ≈0 | ≈0 | ≈0 | 0.22 | 0.11 |
anim-position | 0.10 | ≈0 | 0.55 | 1.35 | 0.54 |
anim-color-large | 5.89 | 1.45 | 4.45 | 5.21 | 2.03 |
anim-size | ≈0 | ≈0 | ≈0 | 0.51 | 0.20 |
anim-retarget | 1.87 | 0.10 | 3.54 | 2.45 | 0.80 |
anim-storm | 0.63 | ≈0 | 1.62 | 1.87 | 1.15 |
list-table-idle | 1.03 | 0.55 | 4.46 | – | 18.5 |
list-table-scroll | 1.17 | 0.47 | 4.44 | – | 19.1 |
list-table-jump | 1.13 | 0.45 | 4.43 | – | 18.1 |
list-table-refresh | 0.33 | 0.12 | 1.45 | – | 4.90 |
list-flat-idle | 125 | 117 | 166 | 127 | 116 |
list-flat-shuffle | 27.3 | 25.1 | 56.0 | 31.5 | 27.5 |
list-flat-grow | ≈0 | ≈0 | ≈0 | ≈0 | ≈0 |
life-layout-noflex | 0.52 | 0.43 | 1.45 | 1.13 | 0.17 |
life-layout | 0.70 | 0.48 | 6.18 | 1.12 | 0.42 |
life-image | 1.99 | 0.91 | 4.02 | 1.65 | 0.80 |
life-label | 22.0 | 19.1 | 21.5 | 23.6 | 14.1 |
life-row | 20.6 | 15.2 | 27.2 | 21.1 | 18.7 |
life-append | 5.59 | 3.42 | 11.1 | 4.71 | 2.15 |
life-churn | ≈0 | ≈0 | ≈0 | ≈0 | ≈0 |
ram-state | 18.2 | 24.2 | – | 27.5 | – |
ram-animated | 17.0 | 6.79 | 21.0 | 14.1 | 6.41 |
ram-rows | 82.5 | 73.4 | 120 | 90.6 | 83.7 |
Memory left after destroying, MB.
| Benchmark | Managed left: V1 | V2 | BSML | Yoga nodes left: V1 | V2 |
|---|---|---|---|---|---|
life-imageMount and destroy 1000 Images | 1.94 | 0 | 0 | 1001 | 0 |
life-labelMount and destroy 1000 Labels | 21.9 | 0 | 0.07 | 1001 | 0 |
life-rowMount and destroy 500 list rows (background, icon, two labels) | 20.5 | 0 | 0.07 | 2001 | 0 |
life-churnCreate and destroy 50 rows every frame | 228 | 0 | 0 | 21401 | 0 |
list-flat-idleFlat list of 2000 rows (8000 nodes), nothing changes | 125 | 0 | 0 | 8002 | 0 |
list-flat-shuffleFlat list of 500 rows, all shifted by one every frame so every row rebinds | 27.9 | 0 | 0 | 2002 | 0 |
list-flat-growFlat list growing by 10 rows every frame | 23.3 | 0 | 0 | 1682 | 0 |
ram-rows2000 list rows (8000 nodes) | 82.3 | 0 | 0 | 8001 | 0 |
Memory per component.
| Component | V1 | V2 | BSML | Quest Reactive | Quest BSML |
|---|---|---|---|---|---|
| Image | 1.1 KB | 942 B | 4.1 KB | 1.8 KB | 947 B |
| Label | 20.2 KB | 19.4 KB | 21.9 KB | 24.4 KB | 14.5 KB |
| Empty container | 750 B | 557 B | 6.3 KB | 1.2 KB | 488 B |
| List row (background, icon, two labels) | 35.1 KB | 31.1 KB | 55.1 KB | 43.4 KB | 39.0 KB |
Allocated per frame. Benchmarks at 10 KB or more on any PC target. Only accurate to a heap block.
| Benchmark | V1 | V2 | BSML |
|---|---|---|---|
state-subscribe | 24227 KB | 0.0 KB | – |
life-churn | 2257 KB | 1645 KB | 10702 KB |
list-table-refresh | 0.5 KB | 0.0 KB | 5756 KB |
list-table-jump | 0.8 KB | 0.1 KB | 4796 KB |
list-flat-grow | 844 KB | 606 KB | 2936 KB |
list-flat-shuffle | 411 KB | 18 KB | 16 KB |
list-table-scroll | 3.5 KB | 2.5 KB | 286 KB |
layout-absolute | 0.7 KB | 146 KB | 0.1 KB |
state-labels | 47 KB | 72 KB | 45 KB |
layout-tree-text | 51 KB | 24 KB | 20 KB |
layout-rows | 28 KB | 19 KB | 0.3 KB |
layout-rect | 17 KB | 2.1 KB | 1.9 KB |
layout-text-one | 4.8 KB | 4.6 KB | 12 KB |
layout-toggle | 11 KB | 1.6 KB | 0.0 KB |
BeatLeader, time.
| Where | Metric | V1, ms | V2, ms | Result |
|---|---|---|---|---|
| Main menu, idle | UI CPU, p50 | 7.38 | 8.04 | same |
| Main menu, idle | UI CPU, mean | 7.87 | 8.36 | same |
| Main menu, idle | UI CPU, p99 | 12.4 | 14.1 | same |
| Main menu, idle | Whole frame, p50 | 9.09 | 9.65 | same |
| Playing a replay | UI CPU, p50 | 11.1 | 11.9 | same |
| Playing a replay | UI CPU, mean | 11.9 | 12.4 | same |
| Playing a replay | UI CPU, p99 | 23.8 | 20.0 | same |
| Playing a replay | Whole frame, p50 | 11.7 | 12.5 | same |
BeatLeader, opening screens. Longest frame in milliseconds.
| Screen | First open: V1 | V2 | Later opens: V1 | V2 | Result, first open |
|---|---|---|---|---|---|
| Hub | 108 | 87.9 | 32.6 | 23.6 | same |
| Settings | 41.3 | 41.6 | 38.4 | 25.9 | same |
| Replay manager | 515 | 522 | 31.5 | 52.1 | same |
| Leaderboard | 1385 | 1415 | 107 | 109 | same |
BeatLeader, memory.
| Where | Metric | V1 | V2 |
|---|---|---|---|
| Main menu | Managed heap | 134 MB | 136 MB |
| Main menu | Unity allocator | 1447 MB | 1448 MB |
| Main menu | Process private bytes | 3038 MB | 3043 MB |
| Main menu | Live Yoga nodes | 432 | 423 |
| Hub | Managed kept by the first open | 0.39 MB | 0.17 MB |
| Settings | Managed kept by the first open | 0.45 MB | 0.55 MB |
| Replay manager | Managed kept by the first open | 2.15 MB | 2.07 MB |
| Leaderboard | Managed kept by the first open | 58.1 MB | 70.6 MB |
| Replay | Managed kept by the first replay | 37.3 to 37.8 MB | 36.2 to 42.3 MB |
| Replay | Managed kept by each later replay | 14.9 to 15.6 MB | at most 3.0 MB |
| Replay | Yoga nodes kept by each replay | 337 | 0 |
V1 on V2's Yoga, layout. Milliseconds per frame.
| Benchmark | V1 | V1 on V2's Yoga | V2 | V1 on V2's Yoga vs V1 | V2 vs V1 on V2's Yoga |
|---|---|---|---|---|---|
layout-leaf | 14.5 | 3.03 | 2.87 | 4.8× faster | same |
layout-root | 14.2 | 3.28 | 5.23 | 4.3× faster | 1.6× slower |
layout-percent | 18.1 | 10.3 | 14.9 | 1.8× faster | 1.4× slower |
layout-deep | 21.3 | 1.06 | 1.81 | 20× faster | same |
layout-tree-fixed | 23.7 | 7.18 | 8.69 | 3.3× faster | 1.2× slower |
layout-tree | 47.0 | 24.9 | 26.8 | 1.9× faster | same |
layout-rows | 35.8 | 19.4 | 21.0 | 1.8× faster | 1.1× slower |
V1 on V2's Yoga, mounts. Milliseconds.
| Benchmark | V1 | V1 on V2's Yoga | V2 |
|---|---|---|---|
life-layoutMount and destroy 1000 empty Layouts | 40.9 | 43.7 | 25.8 |
life-imageMount and destroy 1000 Images | 320 | 312 | 71.8 |
life-labelMount and destroy 1000 Labels | 865 | 764 | 224 |
life-rowMount and destroy 500 list rows (background, icon, two labels) | 1104 | 1204 | 291 |
life-appendMount and destroy 4000 empty Layouts in one container, to show how appending scales against life-layout | 254 | 148 | 102 |
ram-rows2000 list rows (8000 nodes) | 3397 | 4712 | 1412 |