improve performance of style tags that use precedence
Summary
Currently, every time we insert a new style tag that has precedence, we first need to run 2 querySelectorAll calls. If we want to render 400 style tags, we'll need to run querySelectorAll 800 times. This is fairly slow.
Additionally, every time we encounter a new style tag react will create a new style tag in the DOM, and will (as far as I can tell) make no attempt to remove that style tag at any point. So the process of inserting itself will get slower as we insert more and more styles.
We can address the performance in this specific case by inserting nodes less often:
- keep track of all our style tags based on their data-href and data-precedence values
- the first time we acquire a style resource, query the DOM to populate our cache with any tags that may have been server rendered
- if we already have a style node for a given href, we never need to insert a new style tag
- if we already have a style node for a given precedence, we can append our new rule to the existing style tag instead of creating a new node
- when possible, append new styles using insertRule to minimize the amount of parsing the browser needs to do. fall back to appending our style if needed (which is slow, but might be needed if inserting fails)
a few other random notes
- this optimization is aimed at inserting nodes less often, but the actual process of inserting nodes is unchanged and still not that efficient. If you were to, for example, insert 400 stylesheet resources instead of 400 style resources you would encounter a performance issue (because those cannot be optimized in this way)
- I would be interested to see react manage these styles better. perhaps using insertRule and deleteRule to remove/update styles rather than inserting them into the DOM forever. that would be beyond the scope of this PR, of course, but it bothers me that react makes no attempt to clean up these styles when components are unmounted.
fixes #32806 fixes #30739
How did you test this change?
I've built this version of react and patched it into a few projects that were having performance issues. I'm planning on doing this again (I've tweaked a few things) and will update these results when I've done that.
in one such expensive render: the entire process (including the browsers style recalculation) went from 2.11s to 504.34ms the react portion of that process went from 1.47s to 26.91ms flushMutationEffects (the previously slow part) went from 1.45s to 7.58ms
in one more extreme case: the entire process (including the browsers style recalculation) went from 9.57s to 600.18ms the react portion of that process went from 8.63s to 41.05ms flushMutationEffects (the previously slow part) went from 8.61s to 6.67ms
These two examples are obviously not fully comprehensive (I could fabricate a fairly comprehensive example case if anyone is interested) but they do roughly represent the level of improvement I've seen in my testing.
Comparing: bc6184dd993e6ea0efdee7553293676db774c3ca...7c917f53cc17c028db45048a5a1654844cf6c919
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 | +0.57% | 516.14 kB | 519.07 kB | +0.63% | 91.87 kB | 92.44 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 | +0.47% | 621.45 kB | 624.37 kB | +0.51% | 110.00 kB | 110.57 kB |
| facebook-www/ReactDOM-prod.classic.js | +0.45% | 654.82 kB | 657.75 kB | +0.46% | 115.57 kB | 116.10 kB |
| facebook-www/ReactDOM-prod.modern.js | +0.45% | 645.10 kB | 648.03 kB | +0.47% | 114.04 kB | 114.57 kB |
Significant size changes
Includes any change greater than 0.2%:
Expand to show
| Name | +/- | Base | Current | +/- gzip | Base gzip | Current gzip |
|---|---|---|---|---|---|---|
| oss-stable-semver/react-dom/cjs/react-dom-client.production.js | +0.57% | 516.02 kB | 518.94 kB | +0.63% | 91.84 kB | 92.42 kB |
| oss-stable/react-dom/cjs/react-dom-client.production.js | +0.57% | 516.14 kB | 519.07 kB | +0.63% | 91.87 kB | 92.44 kB |
| facebook-react-native/react-dom/cjs/ReactDOMClient-prod.js | +0.54% | 540.22 kB | 543.14 kB | +0.60% | 95.76 kB | 96.34 kB |
| facebook-react-native/react-dom/cjs/ReactDOMProfiling-prod.js | +0.54% | 545.73 kB | 548.65 kB | +0.59% | 96.84 kB | 97.41 kB |
| oss-stable-semver/react-dom/cjs/react-dom-profiling.profiling.js | +0.53% | 548.62 kB | 551.54 kB | +0.60% | 96.70 kB | 97.28 kB |
| oss-stable/react-dom/cjs/react-dom-profiling.profiling.js | +0.53% | 548.74 kB | 551.66 kB | +0.60% | 96.73 kB | 97.31 kB |
| facebook-react-native/react-dom/cjs/ReactDOMClient-profiling.js | +0.52% | 567.26 kB | 570.19 kB | +0.57% | 99.68 kB | 100.24 kB |
| facebook-react-native/react-dom/cjs/ReactDOMProfiling-profiling.js | +0.51% | 573.20 kB | 576.13 kB | +0.55% | 100.84 kB | 101.39 kB |
| oss-experimental/react-dom/cjs/react-dom-client.production.js | +0.47% | 621.45 kB | 624.37 kB | +0.51% | 110.00 kB | 110.57 kB |
| oss-experimental/react-dom/cjs/react-dom-unstable_testing.production.js | +0.46% | 635.86 kB | 638.78 kB | +0.50% | 113.57 kB | 114.13 kB |
| facebook-www/ReactDOM-prod.modern.js | +0.45% | 645.10 kB | 648.03 kB | +0.47% | 114.04 kB | 114.57 kB |
| facebook-www/ReactDOM-prod.classic.js | +0.45% | 654.82 kB | 657.75 kB | +0.46% | 115.57 kB | 116.10 kB |
| facebook-www/ReactDOMTesting-prod.modern.js | +0.44% | 659.50 kB | 662.43 kB | +0.49% | 117.64 kB | 118.21 kB |
| facebook-www/ReactDOMTesting-prod.classic.js | +0.44% | 669.22 kB | 672.15 kB | +0.46% | 119.25 kB | 119.79 kB |
| oss-experimental/react-dom/cjs/react-dom-profiling.profiling.js | +0.43% | 686.17 kB | 689.09 kB | +0.46% | 119.79 kB | 120.35 kB |
| facebook-www/ReactDOM-profiling.modern.js | +0.41% | 718.80 kB | 721.73 kB | +0.42% | 124.30 kB | 124.83 kB |
| facebook-www/ReactDOM-profiling.classic.js | +0.40% | 726.85 kB | 729.77 kB | +0.42% | 125.62 kB | 126.14 kB |
| oss-stable-semver/react-dom/cjs/react-dom-client.development.js | +0.35% | 956.50 kB | 959.84 kB | +0.36% | 161.16 kB | 161.74 kB |
| oss-stable/react-dom/cjs/react-dom-client.development.js | +0.35% | 956.63 kB | 959.96 kB | +0.36% | 161.19 kB | 161.77 kB |
| oss-stable-semver/react-dom/cjs/react-dom-profiling.development.js | +0.34% | 972.94 kB | 976.28 kB | +0.36% | 164.00 kB | 164.59 kB |
| oss-stable/react-dom/cjs/react-dom-profiling.development.js | +0.34% | 973.07 kB | 976.40 kB | +0.36% | 164.03 kB | 164.63 kB |
| facebook-react-native/react-dom/cjs/ReactDOMClient-dev.js | +0.34% | 989.84 kB | 993.18 kB | +0.35% | 166.45 kB | 167.03 kB |
| facebook-react-native/react-dom/cjs/ReactDOMProfiling-dev.js | +0.33% | 1,006.17 kB | 1,009.50 kB | +0.35% | 169.27 kB | 169.86 kB |
| oss-experimental/react-dom/cjs/react-dom-client.development.js | +0.30% | 1,129.72 kB | 1,133.06 kB | +0.31% | 188.57 kB | 189.16 kB |
| oss-experimental/react-dom/cjs/react-dom-profiling.development.js | +0.29% | 1,146.12 kB | 1,149.45 kB | +0.31% | 191.41 kB | 192.00 kB |
| oss-experimental/react-dom/cjs/react-dom-unstable_testing.development.js | +0.29% | 1,146.27 kB | 1,149.60 kB | +0.30% | 192.26 kB | 192.84 kB |
| facebook-www/ReactDOM-dev.modern.js | +0.28% | 1,183.58 kB | 1,186.92 kB | +0.30% | 196.27 kB | 196.86 kB |
| facebook-www/ReactDOM-dev.classic.js | +0.28% | 1,192.72 kB | 1,196.06 kB | +0.30% | 198.05 kB | 198.64 kB |
| facebook-www/ReactDOMTesting-dev.modern.js | +0.28% | 1,200.12 kB | 1,203.45 kB | +0.30% | 200.11 kB | 200.71 kB |
| facebook-www/ReactDOMTesting-dev.classic.js | +0.28% | 1,209.26 kB | 1,212.59 kB | +0.30% | 201.82 kB | 202.43 kB |
Generated by :no_entry_sign: dangerJS against 7c917f53cc17c028db45048a5a1654844cf6c919
I hadn’t needed to set href and precedence in a
This pull request has been automatically marked as stale. If this pull request is still relevant, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize reviewing it yet. Your contribution is very much appreciated.
bump
This pull request has been automatically marked as stale. If this pull request is still relevant, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize reviewing it yet. Your contribution is very much appreciated.
bump