Replace listeners array with a Map for better performance #4476
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.