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

RFC Implementation: Speculative work #18262

Closed
wants to merge 27 commits into from

Conversation

gnoff
Copy link
Collaborator

@gnoff gnoff commented Mar 10, 2020

Summary

This is a long-lived PR to discuss development of an implementation for reactjs/rfcs#150

The current state is a Proof-of-Concept implementation of speculative work along with context-selectors.

There are currently four experiments embedded in this PR. some will be removed soon

selectors for useContext

flag enableContextSelectors

this replaces readContext as the implementation for useContext and replaces with a stateful mount and update hook which can track previous values. It is not inherently 'faster' by itself since it does not affect context propagation and therefore the selector has no opportunity to bail out of work but when used with reifyNextWork or speculativeWork it can help reduce the amount of work done on a render for components that only read a small part of the context value

what's next:
I plan on reimplementing outside of hooks so any context reader can use a selector and take advantage of more aggressive bailouts

Context propagation by Reader

flag enableContextReaderPropagation

this feature replaces the tree-walking context propagation for one that tracks direct readers. It is analagous to each reader being a useReducer hook and having the dispatchAction function held by the Provider. propagation is then mostly just dispatching a new state. It's not literally using this functionality and there are some tricks to make it faster on mount and only do the more complicated tracking in advanced cases when the set of contexts read from changes from render to render.

The key benefit here is the growth in work time for propagation is O(readers) instead of O(size of tree)

what's next:
I plan on moving where we track whether we need advanced mode into the prepare step to solve some uncommon memory leaks
I plan on experimenting with a super fast mode where we only activate reader tracking after a provider has a changed value at least once. this would enable passive contexts to add no mount or unmount costs

Reify Next Work

flag enableReifyNextWork

This is the primary feature of Speculative Work. When this flag is enabled the bailoutOnAlreadyCompleted work will be modified so that before deciding whether to go deeper into the subtree the boundary of the next fibers that have work will be visited with a walking algorithm similar to context propagation. when a fiber with work is found and if that fiber type is supported (FunctionComponent for instance) a semi-eager bailout can run which will determine if that speculative update is now reified? (i.e. an update went from "this might need to an update" to "there is no update" or "there is definitely" an update)

If reification happens we've hit a work boundary and move on. once we exhaust the sub-tree up to the next work boudary and return back to our bailout we see if there is still any work to do deeper. the rest of the bailout logic remains unchanged

this is superior to speculative mode because the tree walking algo is lighter since we aren't doing the full 'work' logic, and because it is simpler in that we don't have to juggle "are we doing work on current or on work in progress"

what's next:
explore doing a full render call during reifyNextWork and stashing the result. if work is needed we can use the precomputed workInProgress instead of doing it a second time. this is actually 'safe' because we no none of the intermediate work could have affected the render
Also look into how we might support class components

DEPRECATED Speculative Work Mode

flag enableSpeculativeWork

This was my initial implementation of speculative work and it was good but reifyNextWork is better. Essentially if we did a bailoutOnAlreadyCompleted work we would not create workInProgress fibers for children but would instead start work on the current tree. Then if we got somewhere where an update on curren required workInProgress we would reify the ancestor current tree back to the last WIP fiber before we started speculative mode and continue from there.

This worked and did offer some performance enhancements in some cases but there are a lot of extra edge cases tracking whether you are in speculative mode or not and how to handle switching between those modes

This feature is technically compatible with reifyNextWork but doesn't really offer any additional performance advantages and so it should probably be abandoned

What's next:
remove from codebase

@codesandbox-ci
Copy link

codesandbox-ci bot commented Mar 10, 2020

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 c1c0529:

Sandbox Source
kind-sunset-ggixb Configuration

packages/react-reconciler/src/ReactFiber.js Outdated Show resolved Hide resolved
packages/react-reconciler/src/ReactFiberBeginWork.js Outdated Show resolved Hide resolved
packages/react-reconciler/src/ReactFiberBeginWork.js Outdated Show resolved Hide resolved
packages/react-reconciler/src/ReactFiberBeginWork.js Outdated Show resolved Hide resolved
packages/react-reconciler/src/ReactFiberHooks.js Outdated Show resolved Hide resolved
packages/react-reconciler/src/ReactFiberHooks.js Outdated Show resolved Hide resolved
packages/react-reconciler/src/ReactFiberWorkLoop.js Outdated Show resolved Hide resolved
packages/react/src/ReactElement.js Outdated Show resolved Hide resolved
@yisar
Copy link
Contributor

yisar commented Mar 13, 2020

There is no doubt that it's interesting. It's a good way to provide reference value through selector and update accurately in a lazy way.

But one thing, I think maybe this is not only applicable to Context, maybe we can also make props cooperate with selector? Or with a similar idea, built-in React.memo, so that components can also render accurately?

And about lazy, can we do it in JSX?
such as https://babeljs.io/docs/en/babel-plugin-transform-react-constant-elements

@gnoff
Copy link
Collaborator Author

gnoff commented Mar 13, 2020

@yisar at the moment selectors for context only work in function components using the useContext hook. users would be free to incorporate props into the selector without limitation much like they can with reducers in useReducer

@stale
Copy link

stale bot commented Aug 3, 2020

This pull request has been automatically marked as stale. If this pull request is still relevant, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize reviewing it yet. Your contribution is very much appreciated.

@stale stale bot added the Resolution: Stale Automatically closed due to inactivity label Aug 3, 2020
@stale
Copy link

stale bot commented Aug 16, 2020

Closing this pull request after a prolonged period of inactivity. If this issue is still present in the latest release, please ask for this pull request to be reopened. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed Resolution: Stale Automatically closed due to inactivity
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants