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 Array.from() from hot path #19908

Merged
merged 2 commits into from
Sep 25, 2020
Merged

Remove Array.from() from hot path #19908

merged 2 commits into from
Sep 25, 2020

Conversation

gaearon
Copy link
Collaborator

@gaearon gaearon commented Sep 25, 2020

We were turning every createEventHandle listener Set into an array during traversal, which is not necessary because we can iterate over the Set itself instead to reduce the GC churn.

The diff is simple but indentation changed so it's a bit messy. I'll comment inline.

@facebook-github-bot facebook-github-bot added CLA Signed React Core Team Opened by a member of the React Core Team labels Sep 25, 2020
@@ -726,8 +726,9 @@ export function accumulateSinglePhaseListeners(
inCapturePhase: boolean,
accumulateTargetOnly: boolean,
): void {
const bubbled = event._reactName;
const captured = bubbled !== null ? bubbled + 'Capture' : null;
const bubbleName = event._reactName;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Just renames for clarity.

if (enableCreateEventHandleAPI) {
const eventHandlerListeners = getEventHandlerListeners(currentTarget);
if (eventHandlerListeners !== null) {
eventHandlerListeners.forEach(entry => {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is the change. Array.from -> Set.forEach.

Copy link
Contributor

@trueadm trueadm Sep 25, 2020

Choose a reason for hiding this comment

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

I benchmarked this before and found looping over Array.from to be much faster than Set.forEach.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

What was the benchmark? Do you mean there is a pronounced difference with the sizes we're operating (5-10 items)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

To be clear my concern isn't the iteration speed itself (I would expect it to be dwarfed by everything else we do, considering the Set has a small size), but that we're allocating a non-empty array for each level that has these handlers, for each event as it goes upwards. This adds up.

Copy link
Contributor

Choose a reason for hiding this comment

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

It probably doesn't make much difference in either case tbh. Allocating small arrays is also cheap and we don't have a good way to measure either case here. To keep things simplistic though, we can keep the forEach as you've done.

const eventHandlerListeners = getEventHandlerListeners(currentTarget);
if (eventHandlerListeners !== null) {
eventHandlerListeners.forEach(entry => {
if (entry.type === targetType && entry.capture === inCapturePhase) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is a simplification. We don't need to do

if (a && b) {
  // do thing
} else if (!a && !b) {
  // do same thing
}

because it is equivalent to

if (a === b) {
  // do thing
}

if (bubbleListener != null) {

// Standard React on* listeners, i.e. onClick or onClickCapture
if (reactEventName !== null) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

There is only one active event name at any given time. I read it early so no need for two conditions here.

}
if (eventHandlerListeners !== null) {
eventHandlerListeners.forEach(entry => {
if (entry.type === targetType && entry.capture === inCapturePhase) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Same change as above.

@@ -844,8 +817,8 @@ export function accumulateTwoPhaseListeners(
dispatchQueue: DispatchQueue,
event: ReactSyntheticEvent,
): void {
const bubbled = event._reactName;
const captured = bubbled !== null ? bubbled + 'Capture' : null;
const bubbleName = event._reactName;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Just for consistency.

@codesandbox-ci
Copy link

codesandbox-ci bot commented Sep 25, 2020

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit aa4bf79:

Sandbox Source
React Configuration

@sizebot
Copy link

sizebot commented Sep 25, 2020

Details of bundled changes.

Comparing: c91c1c4...f82ea53

react-dom

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-dom.development.js -0.0% 0.0% 908.28 KB 908.16 KB 206.39 KB 206.44 KB NODE_DEV
ReactDOMForked-prod.js -0.5% -0.4% 375.79 KB 373.81 KB 69.69 KB 69.4 KB FB_WWW_PROD
react-dom.production.min.js -0.0% -0.0% 122.28 KB 122.25 KB 39.34 KB 39.33 KB NODE_PROD
ReactDOMForked-profiling.js -0.5% -0.4% 389.26 KB 387.28 KB 72.1 KB 71.8 KB FB_WWW_PROFILING
react-dom-server.node.production.min.js 0.0% 0.0% 20.66 KB 20.66 KB 7.65 KB 7.65 KB NODE_PROD
ReactDOMTesting-dev.js -0.2% -0.1% 912.12 KB 910.53 KB 205.29 KB 205.14 KB FB_WWW_DEV
ReactDOMTesting-prod.js -0.5% -0.4% 371.99 KB 370.01 KB 70.21 KB 69.95 KB FB_WWW_PROD
react-dom-unstable-fizz.node.production.min.js 0.0% -0.2% 1.17 KB 1.17 KB 666 B 665 B NODE_PROD
react-dom-unstable-fizz.browser.production.min.js 0.0% 🔺+0.1% 1.22 KB 1.22 KB 711 B 712 B UMD_PROD
react-dom-unstable-fizz.browser.development.js 0.0% +0.1% 4.78 KB 4.78 KB 1.68 KB 1.68 KB NODE_DEV
react-dom.development.js -0.0% 0.0% 954.43 KB 954.31 KB 209 KB 209.05 KB UMD_DEV
react-dom-unstable-fizz.browser.production.min.js 0.0% 🔺+0.3% 1.01 KB 1.01 KB 615 B 617 B NODE_PROD
react-dom.production.min.js -0.0% -0.0% 122.11 KB 122.07 KB 40.09 KB 40.08 KB UMD_PROD
react-dom.profiling.min.js -0.0% -0.0% 127.36 KB 127.33 KB 41.76 KB 41.75 KB UMD_PROFILING
ReactDOMForked-dev.js -0.2% -0.1% 962.75 KB 961.17 KB 214.25 KB 214.1 KB FB_WWW_DEV
react-dom.profiling.min.js -0.0% 0.0% 127.72 KB 127.68 KB 41.01 KB 41.03 KB NODE_PROFILING
ReactDOM-dev.js -0.2% -0.1% 961.26 KB 959.67 KB 214.66 KB 214.51 KB FB_WWW_DEV
ReactDOM-prod.js -0.5% -0.3% 374.58 KB 372.6 KB 69.39 KB 69.16 KB FB_WWW_PROD
ReactDOM-profiling.js -0.5% -0.3% 387.74 KB 385.75 KB 71.82 KB 71.61 KB FB_WWW_PROFILING
react-dom-test-utils.development.js 0.0% 0.0% 70.22 KB 70.22 KB 19.12 KB 19.12 KB UMD_DEV

Size changes (experimental)

Generated by 🚫 dangerJS against f82ea53

@sizebot
Copy link

sizebot commented Sep 25, 2020

Details of bundled changes.

Comparing: c91c1c4...aa4bf79

react-dom

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-dom.development.js -0.0% 0.0% 872.83 KB 872.72 KB 199.89 KB 199.93 KB NODE_DEV
ReactDOMForked-prod.js -0.5% -0.3% 387.12 KB 385.13 KB 71.45 KB 71.21 KB FB_WWW_PROD
react-dom.production.min.js -0.0% -0.0% 117.72 KB 117.68 KB 38.02 KB 38.02 KB NODE_PROD
ReactDOMForked-profiling.js -0.5% -0.3% 400.64 KB 398.65 KB 73.83 KB 73.59 KB FB_WWW_PROFILING
react-dom-server.browser.development.js 0.0% -0.0% 143.14 KB 143.14 KB 36.57 KB 36.57 KB UMD_DEV
react-dom-test-utils.production.min.js 0.0% -0.0% 13.65 KB 13.65 KB 5.31 KB 5.31 KB UMD_PROD
ReactDOMTesting-dev.js -0.2% -0.1% 940.45 KB 938.87 KB 210.76 KB 210.61 KB FB_WWW_DEV
react-dom-test-utils.development.js 0.0% 0.0% 65.05 KB 65.05 KB 18.62 KB 18.62 KB NODE_DEV
ReactDOMTesting-prod.js -0.5% -0.3% 385.05 KB 383.07 KB 72.38 KB 72.16 KB FB_WWW_PROD
react-dom.development.js -0.0% 0.0% 917.24 KB 917.11 KB 202.43 KB 202.47 KB UMD_DEV
react-dom.production.min.js -0.0% -0.0% 117.62 KB 117.58 KB 38.71 KB 38.7 KB UMD_PROD
react-dom.profiling.min.js -0.0% -0.0% 121.51 KB 121.48 KB 39.92 KB 39.91 KB UMD_PROFILING
ReactDOMForked-dev.js -0.2% -0.1% 988.34 KB 986.75 KB 218.96 KB 218.82 KB FB_WWW_DEV
react-dom.profiling.min.js -0.0% -0.0% 121.79 KB 121.75 KB 39.22 KB 39.21 KB NODE_PROFILING
react-dom-server.browser.production.min.js 0.0% 0.0% 19.88 KB 19.88 KB 7.46 KB 7.46 KB UMD_PROD
ReactDOM-dev.js -0.2% -0.1% 986.84 KB 985.26 KB 219.32 KB 219.18 KB FB_WWW_DEV
ReactDOM-prod.js -0.5% -0.3% 385.83 KB 383.85 KB 71.1 KB 70.86 KB FB_WWW_PROD
ReactDOM-profiling.js -0.5% -0.3% 399.04 KB 397.05 KB 73.53 KB 73.28 KB FB_WWW_PROFILING
ReactDOMServer-prod.js 0.0% -0.0% 47.3 KB 47.3 KB 11.04 KB 11.03 KB FB_WWW_PROD
react-dom-test-utils.development.js 0.0% 0.0% 70.21 KB 70.21 KB 19.11 KB 19.11 KB UMD_DEV

ReactDOM: size: 0.0%, gzip: -0.0%

Size changes (stable)

Generated by 🚫 dangerJS against aa4bf79

Don't declare block variables inside loops
@gaearon gaearon merged commit 0a00804 into facebook:master Sep 25, 2020
koto pushed a commit to koto/react that referenced this pull request Jun 15, 2021
* Remove Array.from() from hot path

* Fix build

Don't declare block variables inside loops
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed React Core Team Opened by a member of the React Core Team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants