-
-
Notifications
You must be signed in to change notification settings - Fork 15.3k
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
Replace listeners array with a Map for better performance #4476
Conversation
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 1599aad:
|
nextListeners = new Map() | ||
currentListeners.forEach((listener, key) => { | ||
nextListeners.set(key, listener) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not do this?
nextListeners = new Map() | |
currentListeners.forEach((listener, key) => { | |
nextListeners.set(key, listener) | |
}) | |
nextListeners = new Map(currentListeners) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because in my testing, at least with a Set
instead of a Map
, .forEach
was actually somehow faster 🤷♂️ (as mentioned in the original PR description)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Huh, sounds like a bug in the js engine.
I've been looking at a lot of the react-redux and redux code, and was curious about this implementation split between a You mention
Do you just mean that the linked list is probably the fastest, while using a |
Redux has always used an array of listener callbacks internally. However, as we found out with React-Redux, using
listeners.findIndex(callback)
is bad for performance ( reduxjs/react-redux#1523 , reduxjs/react-redux#1869 ).React-Redux uses a custom linked list internally. That's way more than we need here. But, now that we're assuming modern browsers, it's safe to assume ES2015+ support, and that
Map/Set
s are available.I initially tried
Set<ListenerCallback>
, but found that one test failed... because it was passing in the samejest.fn()
instance twice. ASet
compares by reference, so the callback got removed in the firstunsubscribe()
call and was no longer around for the second.We don't have a formal statement that we support passing in the same callback more than once, but that's the implicit semantics thus far.
Given that, a
Map<number, ListenerCallback>
works equivalently, and we'll just have an incrementing counter for the key.Also, I double-checked perf for cloning
Set
s before I switched over toMap
s. Somewhat surprisingly,existing.forEach(item => newSet.add(item))
runs faster thannew Set(existing)
, or usingfor..of
, so I assume the same is true forMap
s. (I will note that I was creatingSet
s with 1M items, and the difference was like 150ms to 120ms. So I don't expect that to have any meaningful cost here.)In practice, I don't expect this to make a lot of difference, because React-Redux's
<Provider>
normally subscribes directly to the store, and all child components subscribe to that<Provider>
's internalSubscription
instance.But getting rid of this little perf footgun is nice.