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

[Scheduler] Store Tasks on a Min Binary Heap #16245

Merged
merged 2 commits into from Aug 8, 2019

Conversation

@acdlite
Copy link
Member

@acdlite acdlite commented Jul 29, 2019

Switches Scheduler's priority queue implementation (for both tasks and timers) to an array-based min binary heap.

This replaces the naive linked-list implementation that was left over from the queue we once used to schedule React roots. A list was arguably fine when it was only used for roots, since the total number of roots is usually small, and is only 1 in the common case of a single-page app.

Since Scheduler is now used for many types of JavaScript tasks (e.g. including timers), the total number of tasks can be much larger.

Heaps are the standard way to implement priority queues. Insertion is O(1) in the average case (append to the end) and O(log n) in the worst. Deletion is O(log n). Peek is O(1).

@sizebot
Copy link

@sizebot sizebot commented Jul 29, 2019

React: size: -1.4%, gzip: 0.0%

Details of bundled changes.

Comparing: 95767ac...f2318c1

react

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react.development.js -2.3% -1.1% 117.26 KB 114.55 KB 29.39 KB 29.07 KB UMD_DEV
react.production.min.js -1.4% 0.0% 13.03 KB 12.84 KB 5.08 KB 5.08 KB UMD_PROD
react.profiling.min.js -1.3% +0.3% 15.23 KB 15.03 KB 5.61 KB 5.63 KB UMD_PROFILING
react.production.min.js 0.0% -0.0% 6.68 KB 6.68 KB 2.75 KB 2.75 KB NODE_PROD
React-dev.js 0.0% -0.0% 70.14 KB 70.14 KB 17.98 KB 17.98 KB FB_WWW_DEV
React-prod.js 0.0% 0.0% 17.39 KB 17.39 KB 4.54 KB 4.54 KB FB_WWW_PROD
React-profiling.js 0.0% 0.0% 17.39 KB 17.39 KB 4.54 KB 4.54 KB FB_WWW_PROFILING

scheduler

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
SchedulerMock-dev.js -13.1% -6.4% 21.36 KB 18.55 KB 4.66 KB 4.36 KB FB_WWW_DEV
SchedulerMock-prod.js -5.3% 🔺+1.2% 13.94 KB 13.2 KB 2.93 KB 2.96 KB FB_WWW_PROD
scheduler.development.js -9.1% -3.8% 29.96 KB 27.24 KB 7.35 KB 7.07 KB NODE_DEV
scheduler.production.min.js -3.7% 🔺+1.5% 5.68 KB 5.48 KB 2.18 KB 2.21 KB NODE_PROD
Scheduler-dev.js -9.1% -3.9% 30.79 KB 27.98 KB 7.48 KB 7.19 KB FB_WWW_DEV
Scheduler-prod.js -4.5% 🔺+0.1% 17.92 KB 17.12 KB 3.7 KB 3.71 KB FB_WWW_PROD
scheduler-tracing.production.min.js 0.0% 🔺+0.3% 728 B 728 B 382 B 383 B NODE_PROD
scheduler-unstable_mock.development.js -13.0% -5.9% 20.8 KB 18.09 KB 4.56 KB 4.29 KB UMD_DEV
scheduler-tracing.profiling.min.js 0.0% -0.2% 3.26 KB 3.26 KB 1001 B 999 B NODE_PROFILING
scheduler-unstable_mock.production.min.js -4.1% 🔺+2.2% 5.08 KB 4.88 KB 2 KB 2.04 KB UMD_PROD
scheduler-unstable_mock.development.js -13.2% -5.9% 20.61 KB 17.9 KB 4.5 KB 4.24 KB NODE_DEV
scheduler-unstable_mock.production.min.js -4.1% 🔺+1.8% 5.07 KB 4.86 KB 1.94 KB 1.98 KB NODE_PROD

Generated by 🚫 dangerJS

@acdlite acdlite force-pushed the acdlite:scheduler-min-heap branch from 4760cbc to 9bead1f Jul 29, 2019
@@ -0,0 +1,92 @@
/**

This comment has been minimized.

@sebmarkbage

sebmarkbage Jul 30, 2019
Member

Does this mean we've given up on having a single easy to reason about file?

This comment has been minimized.

@acdlite

acdlite Jul 30, 2019
Author Member

Personally I don’t think it’s worth it but I can move this into the same file if you want me to

To maximize adoption, I think polyfilling the native API is the better approach.

@acdlite acdlite force-pushed the acdlite:scheduler-min-heap branch 2 times, most recently from bd83da5 to 0e2c76a Jul 30, 2019
if (typeof continuationCallback === 'function') {
var expirationTime = task.expirationTime;

This comment has been minimized.

@acdlite

acdlite Aug 2, 2019
Author Member

This whole branch is left over from before the "return a continuation" API existed. Instead of popping the task from the queue, checking if there's a continuation, and then rescheduling it, we can peek at the task and only remove it once it completes. If there's a continuation, then the task remains in its current position.


function siftUp(heap, node, index) {
while (index > 0) {
const parentIndex = Math.floor((index + 1) / 2) - 1;

This comment has been minimized.

@sophiebits

sophiebits Aug 3, 2019
Collaborator

Math.floor((index - 1) / 2) is equivalent and simpler?

const first = heap[0];
if (first !== undefined) {
const last = heap.pop();
if (last !== undefined && last !== first) {

This comment has been minimized.

@sophiebits

sophiebits Aug 3, 2019
Collaborator

last === undefined never happens, right?

This comment has been minimized.

@acdlite

acdlite Aug 3, 2019
Author Member

Yeah I think maybe this was to satisfy Flow? Will check

This comment has been minimized.

@acdlite

acdlite Aug 8, 2019
Author Member

Nevermind, forgot Flow array access is intentionally unsafe for exactly this reason :D


function siftDown(heap, node, index) {
const length = heap.length;
while (index < length) {

This comment has been minimized.

@sophiebits

sophiebits Aug 3, 2019
Collaborator

This is never false, right? Should it just be while (true)?

task.next = task.previous = null;
// Null out the callback to indicate the task has been canceled. (Can't remove
// from the queue because you can't remove arbitrary nodes from an array based
// heap, only the first one.)

This comment has been minimized.

@sophiebits

sophiebits Aug 3, 2019
Collaborator

Can't you sift up/down as appropriate?

This comment has been minimized.

@acdlite

acdlite Aug 3, 2019
Author Member

I don’t think so because I don’t know the index of the task.

@acdlite
Copy link
Member Author

@acdlite acdlite commented Aug 3, 2019

Will wait to merge this until after 16.9 is released

Switches Scheduler's priority queue implementation (for both tasks and
timers) to an array-based min binary heap.

This replaces the naive linked-list implementation that was left over
from the queue we once used to schedule React roots. A list was arguably
fine when it was only used for roots, since the total number of roots is
usually small, and is only 1 in the common case of a single-page app.

Since Scheduler is now used for many types of JavaScript tasks (e.g.
including timers), the total number of tasks can be much larger.

Binary heaps are the standard way to implement priority queues.
Insertion is O(1) in the average case (append to the end) and O(log n)
in the worst. Deletion is O(log n). Peek is O(1).
@acdlite acdlite force-pushed the acdlite:scheduler-min-heap branch from 0e2c76a to a5a769f Aug 8, 2019
@acdlite acdlite force-pushed the acdlite:scheduler-min-heap branch from a5a769f to f2318c1 Aug 8, 2019
@acdlite acdlite merged commit d77c623 into facebook:master Aug 8, 2019
12 checks passed
12 checks passed
ci/circleci: build Your tests passed on CircleCI!
Details
ci/circleci: flow Your tests passed on CircleCI!
Details
ci/circleci: lint Your tests passed on CircleCI!
Details
ci/circleci: lint_build Your tests passed on CircleCI!
Details
ci/circleci: process_artifacts Your tests passed on CircleCI!
Details
ci/circleci: setup Your tests passed on CircleCI!
Details
ci/circleci: test_build Your tests passed on CircleCI!
Details
ci/circleci: test_build_prod Your tests passed on CircleCI!
Details
ci/circleci: test_dom_fixtures Your tests passed on CircleCI!
Details
ci/circleci: test_source Your tests passed on CircleCI!
Details
ci/circleci: test_source_persistent Your tests passed on CircleCI!
Details
ci/circleci: test_source_prod Your tests passed on CircleCI!
Details
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

None yet

5 participants
You can’t perform that action at this time.