Skip to content

Conversation

@rubennorte
Copy link
Contributor

Summary

In React Native, public instances and internal host nodes are not represented by the same object (ReactNativeElement & shadow nodes vs. just DOM elements), and the only one that's required for rendering is the shadow node. Public instances are generally only necessary when accessed via refs or events, and that usually happens for a small amount of components in the tree.

This implements an optimization to create the public instance on demand, instead of eagerly creating it when creating the host node. We expect this to improve performance by reducing the logic we do per node and the number of object allocations.

How did you test this change?

Manually synced the changes to React Native and run Fantom tests and benchmarks, with the flag enabled and disabled. All tests pass in both cases, and benchmarks show a slight but consistent performance improvement.

@react-sizebot
Copy link

react-sizebot commented Feb 12, 2025

Comparing: 192555b...e0e5a64

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable/react-dom/cjs/react-dom.production.js = 6.68 kB 6.68 kB = 1.83 kB 1.83 kB
oss-stable/react-dom/cjs/react-dom-client.production.js = 515.62 kB 515.62 kB = 92.08 kB 92.07 kB
oss-experimental/react-dom/cjs/react-dom.production.js = 6.69 kB 6.69 kB = 1.83 kB 1.83 kB
oss-experimental/react-dom/cjs/react-dom-client.production.js = 558.66 kB 558.66 kB = 99.31 kB 99.31 kB
facebook-www/ReactDOM-prod.classic.js = 636.61 kB 636.61 kB = 112.06 kB 112.06 kB
facebook-www/ReactDOM-prod.modern.js = 626.93 kB 626.93 kB = 110.48 kB 110.48 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
react-native/implementations/ReactFabric-prod.fb.js +0.28% 378.84 kB 379.91 kB +0.24% 65.87 kB 66.03 kB
react-native/implementations/ReactFabric-profiling.fb.js +0.27% 404.32 kB 405.41 kB +0.22% 69.71 kB 69.87 kB

Generated by 🚫 dangerJS against a5faff6

publicInstance: PublicInstance,
// Exposed through refs. Potentially lazily created.
publicInstance: PublicInstance | null,
// This is only necessary to lazily create `publicInstance`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add docs that this will be nulled out once publicInstance is created?

// Exposed through refs. Potentially lazily created.
publicInstance: PublicInstance | null,
// This is only necessary to lazily create `publicInstance`.
publicRootInstance?: PublicRootInstance | null,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there anyway we could retrieve this lazily from internalInstanceHandle for example?

This is an extra slot for every single host component, which we should avoid if we can.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately internalInstanceHandle is pretty much an opaque object here, so we can't tap into the internals. Also, even if we did, the Fiber itself doesn't have a reference to the root, and we'd have to traverse the fiber tree up to it to access this, which would be inefficient.

The benchmarks I used took this field into account already, and even with this it should be a net win.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't expect this to show on benchmarks, but rather as memory overhead (increased overall heap size).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I only meant runtime performance when I was referencing the benchmarks. What I mean is that this change will overall reduce memory usage, even with that new field, as we'll avoid a large amount of allocations for unused refs.

@rubennorte rubennorte force-pushed the fabric-lazy-public-instance branch from 31fa126 to a5faff6 Compare February 12, 2025 13:30
@rubennorte rubennorte merged commit f83903b into facebook:main Feb 12, 2025
190 checks passed
@rubennorte rubennorte deleted the fabric-lazy-public-instance branch February 12, 2025 13:54
github-actions bot pushed a commit that referenced this pull request Feb 12, 2025
## Summary

In React Native, public instances and internal host nodes are not
represented by the same object (ReactNativeElement & shadow nodes vs.
just DOM elements), and the only one that's required for rendering is
the shadow node. Public instances are generally only necessary when
accessed via refs or events, and that usually happens for a small amount
of components in the tree.

This implements an optimization to create the public instance on demand,
instead of eagerly creating it when creating the host node. We expect
this to improve performance by reducing the logic we do per node and the
number of object allocations.

## How did you test this change?

Manually synced the changes to React Native and run Fantom tests and
benchmarks, with the flag enabled and disabled. All tests pass in both
cases, and benchmarks show a slight but consistent performance
improvement.

DiffTrain build for [f83903b](f83903b)
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.

4 participants