Skip to content

React Compiler: Store aliasing values in an inline set - #37366

Open
andrewimm wants to merge 2 commits into
react:mainfrom
andrewimm:andrewimm/more-alloc-cleanup
Open

React Compiler: Store aliasing values in an inline set#37366
andrewimm wants to merge 2 commits into
react:mainfrom
andrewimm:andrewimm/more-alloc-cleanup

Conversation

@andrewimm

Copy link
Copy Markdown
Collaborator

97.6% of the value sets tracked per identifier in mutation / aliasing inference hold exactly one element, but each was a FxHashSet, meaning each was a heap allocation.
Because the inference code retains a full state in each basic block, these single-element hashsets were a major contributor to peak memory allocation.

This replaces them with a small inline set inspired by smolvec / tinyvec. Five values are stored inline, and any more spill to the heap. This was only needed in 0.02% of sets in my data corpus.

This also makes iteration order match the TS implementation. TS uses Set and iterates in insertion order; the Fx set iterated in hash order. This brings the two behaviors in line.

Benchmark Peak allocation Allocation count Wall time
legacy/image.tsx 33.40 -> 28.07 MiB (-16.0%) -66.7% -28.8%
next-client 33.40 -> 28.07 (-16.0%) -42.0% -15.7%
devtools 16.29 -> 14.25 (-12.5%) -19.9% -7.3%
fixtures 9.41 -> 7.90 (-16.0%) -7.3% -3.3%
next-examples 4.85 -> 4.85 ( 0.0%) -4.8% -1.6%

97.6% of the value sets tracked per identifier in mutation / aliasing
inference hold exactly one element, but each was a `FxHashSet`, meaning
each was a heap allocation.
Because the inference code retains a full state in each basic block,
these single-element hashsets were a major contributor to peak memory
allocation.

This replaces them with a small inline set inspired by smolvec / tinyvec.
Five values are stored inline, and any more spill to the heap. This was
only needed in **0.02%** of sets in my data corpus.

This also makes iteration order match the TS implementation. TS uses
`Set` and iterates in insertion order; the Fx set iterated in hash
order. This brings the two behaviors in line.

| Benchmark        | Peak allocation            | Allocation count | Wall time |
|------------------|----------------------------|------------------|-----------|
| legacy/image.tsx | 58.47 -> 53.25 MiB (-8.9%) | -31.6%           | -21.1%    |
| next-client      | 58.47 -> 53.11 (-9.2%)     | -25.0%           | -14.0%    |
| devtools         | 26.58 -> 24.52 (-7.8%)     | -15.6%           |  -8.6%    |
| fixtures         | 17.47 -> 15.95 (-8.7%)     |  -7.8%           |  -3.1%    |
| next-examples    |  6.22 ->  5.98 (-3.9%)     |  -5.2%           |  -1.1%    |
@meta-cla meta-cla Bot added the CLA Signed label Aug 25, 2026
Building on the `ValueIdSet`, let's store the spilled values in a
boxed slice instead of a Vec. A Vec has 32 bytes, a Boxed slice
only has 24. This is another 7-9% memory savings on top of the
previous commit.
@andrewimm

Copy link
Copy Markdown
Collaborator Author

Building on the ValueIdSet, let's store the spilled values in a
boxed slice instead of a Vec. A Vec has 32 bytes, a Boxed slice
only has 24. This is another 7-9% memory savings on top of the
previous commit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant