-
-
Notifications
You must be signed in to change notification settings - Fork 15.2k
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
Avoid unnecessary calls to listeners #1867
Avoid unnecessary calls to listeners #1867
Conversation
var listeners = currentListeners = nextListeners | ||
for (var i = 0; i < listeners.length; i++) { | ||
listeners[i]() | ||
listeners = currentListeners.slice() |
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.
We used to do this, but it was removed for performance and memory reasons in c031c0a. You're creating multiple duplicate arrays just to avoid race conditions when nesting your dispatches. The simple for loop from before is easier to read and faster.
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.
Actually implications are much more than this!
Lets think about the nested calls to dispatch. When you do
listeners = currentListeners.slice()
you're saying: no matter who was already notified, we now have to notify everyone about the new change, and we do not have to notify everyone about every change...
This copy of the array also allows to check whether a listener being unsubscribed was already notified or not about the last state change.
At the same time, the time needed to shallow-copy an array is far less than the one needed to go through useless notifications, function calls, state checks within listeners, and so on.
It is a bit more pretending from a memory efficiency point of view, though.
To improve things we can avoid the use of shift()
and revert back to listeners[i]()
having the value of i
controlled among the different nested dispatches.
I can make this change this evening...
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.
I've tested the use of the for loop with this branch
See changes here
I'm really not liking this part of the proposal. Many times subscribers are used to integrate external libraries with Redux. And these external libraries normally have some of their own state that is stored externally and make conditional decisions based on that external state, particular to the needs of that library. By attempting to optimize on the user's behalf, we take away some control that they have to behave different depending on state external to the store. This would end up breaking certain types of integrations. It's simpler and more powerful to leave the referential equality check as an option to the user. |
Also, definitely see #1729, as it is directly related to this. I know @acdlite also wants to nail down the exact contract provided by For this effort, as @gaearon has stated, we should rewrite the tests (and allow them to break) to ensure that contract is codified. A test-first approach is best here because once that test suite passes, we can know we have the expected behavior. We just need a comprehensive set of tests. |
Edit: better looking at handleChange from At the same time, relying on (random) false positive triggers from redux to update |
483c387
to
eb5a5ea
Compare
Ok, this seems the golden proposal to me :-)
Thanks @timdorr for your comments: without them I wouldn't have pushed too much for the optimization of array copies! |
Let's see what happens in #1729 for now. I would submit some PRs to /~https://github.com/acdlite/change-emitter to get the ball moving. |
This PR is a better version of #1626.
Please see the original comment for explanation of aims and proposed solution (mainly related to efficiency and effectiveness of code).
In my opinion the present proposal lowers the computational complexity of the recursive dispatch mechanism and prevents listeners to be notified more than once with the very same state value during the same call to dispatch.
The cost to be paid is the use of slice and shift operations on listener arrays which is anyway O(N) (with N the number of registered listeners) and usually in the order of microseconds.
There is another interesting issue which is solved by the present proposal. Consider the following example:
With the current code, in case both condition1 and condition2 result true at the same time for a particular state, listener2 will be notified with the new state value as set because of actionYYY and not as it was set because of actionXXX (when the first listener decided it was the right time to unsubscribe listener2...).
With the new code, listener2 will be notified its last time right before its actual unsubscription, hence with the state as set by actionXXX.
But that's not all.
As a general note I think we should provide a more detailed contract for the subscribe and unsubscribe API calls and listeners notifications.
With the present proposal we could be able to state:
By further modifying dispatch to look like this:
at no additional cost, we could change 5. to:
5. listeners will never be notified twice with the same state value
Obvioulsy, secific tests should be added to support these statements.
...and I'll be happy to add them in case you're interested to proceed further with this.