Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

perf($rootScope): make postDigestQueue more efficient #14545

Closed
wants to merge 1 commit into from

Conversation

thorn0
Copy link
Contributor

@thorn0 thorn0 commented Apr 29, 2016

What kind of change does this PR introduce? (Bug fix, feature, docs update, ...)

Performance improvement

What is the current behavior? (You can also link to an open issue here)
#14534

What is the new behavior (if this is a feature change)?

Using this implementation of queue instead of an array.

Does this PR introduce a breaking change?

postDigestQueue is available as $rootScope.$$postDigestQueue. So it can be considered a breaking change.

Please check if the PR fulfills these requirements

@gkalpak
Copy link
Member

gkalpak commented May 14, 2016

This looks pretty good to me. I think they tiny extra bytes are totally worth it if we can get a speed boost on every $digest.

OOC, have you done any benchmarks ?

@thorn0
Copy link
Contributor Author

thorn0 commented May 14, 2016

There is a benchmark on the page from where I took the implementation of queue.

@jbedard
Copy link
Collaborator

jbedard commented May 14, 2016

Since postDigestQueue is only used in one location and it is emptied each and every time... why not just loop through it and then clear it once at the end?

for (var i=0, len=postDigestQueue.length; i<len; i++) {
   try {
      postDigestQueue[i]();
   } catch (e) {
       $exceptionHandler(e);
   }
}
postDigestQueue.length = 0;

That seems simpler and does only one .length = 0 instead of .slice(offset) log(n) times?

@thorn0
Copy link
Contributor Author

thorn0 commented May 14, 2016

Because this loop can be called recursively inside postDigestQueue[i]();.

@jbedard
Copy link
Collaborator

jbedard commented May 14, 2016

Ah, no wonder that simple solution was never used ^_^

@lgalfaso
Copy link
Contributor

This change looks interesting, but the code at http://code.stephenmorley.org/javascript/queues/ is not MIT licensed, so it cannot be merged into Angular.

@thorn0
Copy link
Contributor Author

thorn0 commented May 14, 2016

On the other hand, it's quite difficult to implement queue in such a way that it wouldn't resemble and look like a work derived from this CC0 licensed code.

@thorn0 thorn0 force-pushed the perf-postDigestQueue branch 2 times, most recently from b31d605 to 0f25be0 Compare May 14, 2016 18:17
@thorn0
Copy link
Contributor Author

thorn0 commented May 14, 2016

@lgalfaso I rewrote it a bit. Please have a look. Now slice isn't called unless the underlying array is longer than 200, which makes it even faster because previously slice was called too often for short queues.

@gkalpak
Copy link
Member

gkalpak commented May 16, 2016

Similar to what @jbedard proposed, couldn't we just empty the queue when it's empty ?
I mean, we know that it is always traversed until it's emptied and previous values are already being set to null (so the memory consumption shouldn't be a concern).

Not a big deal, but it could save a few .slice() calls for really large queues.

@petebacondarwin
Copy link
Contributor

I would be interested in benchmarking this against a linked list queue.

@thorn0
Copy link
Contributor Author

thorn0 commented May 16, 2016

@petebacondarwin Linked list seems to be a bit slower than that queue. You can get the script I used for benchmarking from here.

@thorn0 thorn0 force-pushed the perf-postDigestQueue branch from 0f25be0 to d8eec2e Compare May 16, 2016 20:52
@thorn0
Copy link
Contributor Author

thorn0 commented May 16, 2016

@gkalpak Although my benchmark script doesn't show any improvement from doing this, I moved the slice call to a separate method purge, which has to be called manually.

Also I used this queue implementation for asyncQueue and applyAsyncQueue.

@dcherman
Copy link
Contributor

As a play on what @jbedard suggested, what if you looped through the $$postDigestQueue but stored the variable pointing to head in a shared closure so that each call to $digest would start draining the queue at the appropriate location?

// At the top of the `$RootScopeProvider` closure
var postDigestQueuePosition = 0;

while(postDigestQueuePosition < postDigestQueue.length) {
  try {
    postDigestQueue[postDigestQueuePosition++]();
  } catch (e) {
    $exceptionHandler(e);
  }
}

postDigestQueue.length = postDigestQueuePosition = 0;

How does that compare perf wise to the queue? It's also a more minimalistic change

@thorn0 thorn0 force-pushed the perf-postDigestQueue branch from c5fa76b to af7db57 Compare May 17, 2016 17:47
@thorn0
Copy link
Contributor Author

thorn0 commented May 17, 2016

@dcherman You're right. What you wrote makes perfect sense. I've updated the commit. Of course, it's better than the queue as it doesn't involve any calls to methods like slice. Your solution retains the references to processed items until the array is cleared, but I'm not sure whether it's important.

@jbedard
Copy link
Collaborator

jbedard commented May 17, 2016

Nice 👍

@petebacondarwin
Copy link
Contributor

This looks pretty good now. Thanks @thorn0

@@ -848,13 +849,14 @@ function $RootScopeProvider() {

clearPhase();

while (postDigestQueue.length) {
while (postDigestQueuePosition < postDigestQueue.length) {
Copy link
Member

Choose a reason for hiding this comment

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

Why not keep postDigestQueuePosition local and use a for loop as with asyncQueue above ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See above. This loop can be reentered recursively.

Copy link
Member

Choose a reason for hiding this comment

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

You are right. Which makes me wonder: Could the same happen with the asyncQueue ?

Copy link
Contributor

Choose a reason for hiding this comment

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

No - the asyncQueue is drained during the $digest cycle where postDigestQueue is drained outside of it. As a result, you can't recurse into it since you'd have gotten a digest in progress error.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've added these explanations as comments in the code.

@thorn0 thorn0 force-pushed the perf-postDigestQueue branch from af7db57 to d0dd06a Compare May 18, 2016 21:08
@thorn0 thorn0 force-pushed the perf-postDigestQueue branch from d0dd06a to df5864e Compare May 19, 2016 11:21
petebacondarwin pushed a commit that referenced this pull request May 25, 2016
By using a pointer to the current start of the queue and only clearing
up the queue storage later, we save lots of time that was spent manipulating
arrays with `slice`

Closes #14545
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants