Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove event pooling #13613

Closed
wants to merge 3 commits into from
Closed

Conversation

poeschko
Copy link
Contributor

I started working on this idea by @philipp-spiess during a joint programming session at the React Vienna meetup. It's my first pull request for React, so please go easy on me. :-)

The idea is to get rid of the event object pooling mechanism and instead just create new instances whenever needed. I kept the methods persist (issueing a warning for now) and isPersistent (just always returning true now) on SyntheticEvent since at least persist is documented. That whole section in the documention would still have to be updated (or removed).

Main benefits:

  • reduced code complexity
  • smaller bundle size (react-dom.production.min.js: 92.2 KB -> 91.54 KB, gzipped 30.01 KB -> 29.81 KB; that's -0.7%)

I removed quite a few tests that were mostly concerned with checking the persistence of events, since that's not really needed anymore. Instead, I added a single test that checks the result from isPersistent being true and that persist issues a warning (which would still need to be finalized, if we want it at all).

The hope is that modern JS engines are optimizing object creation & release enough so that not maintaining a pool does not make a significant difference. (Longer-term, maybe JS engines could make this even faster than the pool implemented in user land?)

So far, I quickly tested the performance using the "Event Pooling" fixture in Mac Chrome 69 (moving the mouse around for a few seconds, creating a few hundred events) and did not notice a significant effect, certainly not visually and also not when glancing at Chrome's Performance flamechart (also tried with 6x CPU slowdown); I did not notice significant garbage collection activity either. But these are just some very preliminary observations (in a rather simple scenario), so the performance side would still need to be analyzed much more carefully, also across different operating systems and browsers.

To make the quick performance check using the fixture UI a bit more meaningful, I added a checkbox to choose the React production build instead of the development build (which remains the default). Please let me know if this should rather go into a separate PR (or if it's not desired at all).

This would take care of #13224 (where the idea of getting rid of object pooling is also mentioned).

I realize that this is quite a big change, so maybe it should have started as an RFC or so. But even if there's more discussion needed, I guess having the code along with it doesn't hurt. Just let me know and I could continue trying to measure the performance impact more carefully (suggestions welcome). But no hard feelings if this is rejected for whatever reason.

Thanks to @philipp-spiess for the introduction to the React code base and kicking off this idea!

@pull-bot
Copy link

pull-bot commented Sep 11, 2018

ReactDOM: size: -0.7%, gzip: -0.7%

Details of bundled changes.

Comparing: f6fb03e...6dc4a99

react-dom

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-dom.development.js -0.6% -0.5% 647.03 KB 643.18 KB 151.75 KB 150.96 KB UMD_DEV
react-dom.production.min.js -0.7% -0.7% 92.2 KB 91.54 KB 30.01 KB 29.81 KB UMD_PROD
react-dom.development.js -0.6% -0.5% 642.4 KB 638.54 KB 150.36 KB 149.58 KB NODE_DEV
react-dom.production.min.js -0.7% -0.6% 92.19 KB 91.53 KB 29.68 KB 29.5 KB NODE_PROD
react-dom-test-utils.development.js -7.8% -6.2% 44.66 KB 41.16 KB 12.19 KB 11.44 KB UMD_DEV
react-dom-test-utils.production.min.js -5.7% -4.8% 9.98 KB 9.42 KB 3.71 KB 3.54 KB UMD_PROD
react-dom-test-utils.development.js -7.9% -6.2% 44.38 KB 40.88 KB 12.13 KB 11.38 KB NODE_DEV
react-dom-test-utils.production.min.js -7.8% -6.6% 9.76 KB 8.99 KB 3.65 KB 3.4 KB NODE_PROD
react-dom-unstable-native-dependencies.development.js -6.0% -4.6% 60.27 KB 56.67 KB 15.79 KB 15.07 KB UMD_DEV
react-dom-unstable-native-dependencies.production.min.js -6.3% -5.0% 11 KB 10.31 KB 3.8 KB 3.61 KB UMD_PROD
react-dom-unstable-native-dependencies.development.js -6.0% -4.6% 59.94 KB 56.35 KB 15.67 KB 14.94 KB NODE_DEV
react-dom-unstable-native-dependencies.production.min.js -8.2% -6.6% 10.74 KB 9.86 KB 3.7 KB 3.45 KB NODE_PROD
ReactDOM-dev.js -0.7% -0.5% 664.73 KB 660.41 KB 152.76 KB 151.93 KB FB_WWW_DEV
ReactDOM-prod.js -0.5% -0.5% 286.06 KB 284.68 KB 53.13 KB 52.86 KB FB_WWW_PROD
ReactTestUtils-dev.js -9.5% -7.0% 41.47 KB 37.54 KB 11.18 KB 10.4 KB FB_WWW_DEV
ReactDOMUnstableNativeDependencies-dev.js -7.0% -5.3% 57.62 KB 53.6 KB 14.62 KB 13.84 KB FB_WWW_DEV
ReactDOMUnstableNativeDependencies-prod.js -6.5% -6.5% 26.27 KB 24.55 KB 5.3 KB 4.96 KB FB_WWW_PROD
react-dom.profiling.min.js -0.7% -0.6% 95.23 KB 94.57 KB 30.35 KB 30.17 KB NODE_PROFILING
ReactDOM-profiling.js -0.5% -0.5% 292.94 KB 291.56 KB 54.6 KB 54.32 KB FB_WWW_PROFILING

react-native-renderer

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
ReactNativeRenderer-dev.js -0.9% -0.8% 499.47 KB 494.95 KB 110.72 KB 109.87 KB RN_FB_DEV
ReactNativeRenderer-prod.js -0.9% -1.2% 214.83 KB 212.94 KB 37.46 KB 37.02 KB RN_FB_PROD
ReactNativeRenderer-dev.js -0.9% -0.8% 499.2 KB 494.68 KB 110.65 KB 109.8 KB RN_OSS_DEV
ReactNativeRenderer-prod.js -0.9% -1.2% 204.65 KB 202.76 KB 35.81 KB 35.37 KB RN_OSS_PROD
ReactFabric-dev.js -0.9% -0.8% 489.73 KB 485.21 KB 108.33 KB 107.48 KB RN_FB_DEV
ReactFabric-prod.js -1.0% -1.2% 197.07 KB 195.18 KB 34.35 KB 33.95 KB RN_FB_PROD
ReactFabric-dev.js -0.9% -0.8% 489.77 KB 485.25 KB 108.34 KB 107.5 KB RN_OSS_DEV
ReactFabric-prod.js -1.0% -1.2% 197.1 KB 195.21 KB 34.36 KB 33.96 KB RN_OSS_PROD
ReactNativeRenderer-profiling.js -0.9% -1.2% 212.38 KB 210.49 KB 37.34 KB 36.91 KB RN_OSS_PROFILING
ReactFabric-profiling.js -0.9% -1.1% 204.28 KB 202.39 KB 35.9 KB 35.5 KB RN_OSS_PROFILING
ReactNativeRenderer-profiling.js -0.9% -1.1% 220.85 KB 218.96 KB 38.85 KB 38.41 KB RN_FB_PROFILING
ReactFabric-profiling.js -0.9% -1.1% 204.24 KB 202.35 KB 35.88 KB 35.48 KB RN_FB_PROFILING

schedule

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
schedule.development.js n/a n/a 0 B 19.17 KB 0 B 5.74 KB UMD_DEV
schedule.production.min.js n/a n/a 0 B 3.16 KB 0 B 1.53 KB UMD_PROD

Generated by 🚫 dangerJS

# Conflicts:
#	fixtures/dom/.gitignore
#	fixtures/dom/package.json
#	fixtures/dom/src/react-loader.js
@philipp-spiess
Copy link
Contributor

Awesome Jan, thanks for filing the PR 🙌. As I've already told you yesterday it might take a while until we have all the edge cases ironed out and can merge this - and there's a possibility that this will never work out. I hope this doesn't demotivate you to continue working on it. 🙂

I had this idea for a while and I'm glad we have a starting point for the discussion now. Ideally we can also get rid of the event property lists as well after removing pooling - So that we only end up adding the shims but remove properties with no change on the React side. One idea to achieve this is to use a prototype linked object like this this:

let synthethicEvent = Object.create(nativeEvent);
synthethicEvent.stopPropagation = () => ...;

But let's start with removing pooling for now. I think the next step is to get an understanding of the performance characteristics of this change. I'm having two different steps in mind that should give us an idea wether the event pooling added a significant difference to the event system or not:

  1. To simulate a real world setup, we use a onMouseMove tracker in combination with the Chrome developer tools. Comparing screenshots of the performance recordings with or without these changes should already be helpful - That's what we've already started yesterday. I think we should also have a look at the memory recordings since the code comments refer to garbage collection specifically.

Garbage collection during the events would lead to notable pauses so we also need to find out when the GC is triggered - Maybe we can compare this as well?

It would be great if you can start working on this and post the devtools screenshots here so we get an idea. We want to test this for production but also for development builds so we don't regress anything. Feel free to reach out if you have questions or need help!
2. We don't want to regress older browsers as well. They tend to not having great performance tools so I've set up a simple benchmark.js system that would allow us to compare the two different implementations by manually dispatching events. This is working on IE9+ and should tell more about these browsers.

Oh and we should probably add this behind the 🔥 React Fire feature flag 🙂 (#13554).

@poeschko
Copy link
Contributor Author

I'm a bit stuck investigating the build failure during yarn test-build-prod. (There's no failure for yarn test or yarn test-build.)

To debug this, I ended up adding some console.log statements to the generated build/node_modules/react-dom/cjs/react-dom.production.min.js and react-dom-test-utils.production.min.js. It seems that for some invocations of the function returned by makeSimulator (ReactTestUtils.js), the SyntheticEvent function is not what I'd expect it to be; it does not have an Interface property, so no properties are copied over from the native event. This causes test failures e.g. when target and type are not defined on the synthetic event.

I don't know what this is caused by... Since it only happens for the production build, I guess it has something to do with JS minification.

@gaearon
Copy link
Collaborator

gaearon commented Sep 11, 2018

We were thinking it might be more practical to start from scratch instead of simplifying the existing event system.

@jquense
Copy link
Contributor

jquense commented Sep 11, 2018

i'd looooove to try that ;)

@poeschko
Copy link
Contributor Author

Understood – a bit sad but also exciting! So I'll stop pursueing this for now. Feel free to close this PR if it's not needed. Happy to help with something else.

@nhunzaker
Copy link
Contributor

nhunzaker commented Sep 11, 2018

We were thinking it might be more practical to start from scratch instead of simplifying the existing event system.

Oooh nice. How When can we start?

@gaearon
Copy link
Collaborator

gaearon commented Sep 11, 2018

I'll write something up in the next few days.

@poeschko
Copy link
Contributor Author

Should I extract my changes to the fixtures GUI (so you can choose the production build of React) into a separate pull request? I found it very useful while testing the event pooling changes, but it's probably useful in other cases as well.

@nhunzaker
Copy link
Contributor

@poeschko I think a toggle to switch between development and production React would be awesome.

@philipp-spiess
Copy link
Contributor

@poeschko If you're still interested in landing the DOM fixture change, take a look at this commit. It's basically your changes cherry picked onto the latest master plus a fix for React versions <16. I've also deployed it for you all to test it 🙂

https://philipp-react-dom-fixtures.netlify.com/

@poeschko
Copy link
Contributor Author

poeschko commented Oct 6, 2018

Thanks @philipp-spiess! I opened #13786 with just that commit.

Closing this pull request.

@poeschko poeschko closed this Oct 6, 2018
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.

7 participants