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

Improve unmasked context caching #8723

Merged
merged 1 commit into from
Jan 9, 2017

Conversation

bvaughn
Copy link
Contributor

@bvaughn bvaughn commented Jan 9, 2017

  • Only store cached masked/unmasked contexts on context consumers.
  • Move all references to __reactInternal* cached attributes inside of ReactFiberContext.

Related to this discussion thread.

@@ -57,6 +57,18 @@ function getUnmaskedContext(workInProgress : Fiber) : Object {
}
exports.getUnmaskedContext = getUnmaskedContext;

function cacheContext(workInProgress : Fiber, unmaskedContext : Object, maskedContext : Object) {
if (isContextConsumer(workInProgress)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

We already doing an equivalent check in getMaskedContext which is called just before cacheContext.
Can we avoid doing it twice?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The thing that makes this tricky is that instance won't always exist yet when getMaskedContext is called. That's the whole reason the separate function exists at all.

What would you suggest?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Right, but we don’t need the instance to tell if it’s a context consumer or not.
Or do we?

I thought that maybe we can make getMaskedContext return null for non-context consumers. Then we’d know for sure that we don’t need to call cacheContext.

Not saying it’s the best way, just a proposal.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You are correct. We do not need instance for that. I just mentioned that we need to do some kind of duplicate work since instance won't always exist.

getMaskedContext returns emptyObject for non-consumers. We could compare it? eg

function cacheContext(workInProgress : Fiber, unmaskedContext : Object, maskedContext : Object) {
  if (maskedContext !== emptyObject) {
    const instance = workInProgress.stateNode;

    if (instance) {
      instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;
      instance.__reactInternalMemoizedMaskedChildContext = maskedContext;
    }
  }
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

We could but it seems a bit fishy to make one perf optimization to rely on another perf optimization being present in another file. Returning null since more explicit if we want to "signal" that it's not a context consumer.

Copy link
Collaborator

@gaearon gaearon Jan 9, 2017

Choose a reason for hiding this comment

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

Alternatively we could call isContextConsumer from outside in the first place.

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 assume that getMaskedContext returns emptyObject instead of null for a reason.

We could but it seems a bit fishy to make one perf optimization to rely on another perf optimization being present in another file.

This all inside of ReactFiberContext. What's the other file?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah! I think I understand.

Copy link
Collaborator

@gaearon gaearon Jan 9, 2017

Choose a reason for hiding this comment

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

I assume that getMaskedContext returns emptyObject instead of null for a reason.

Because we want to pass an empty object to components. But we could do || emptyObject this here (and similar places) instead.

This all inside of ReactFiberContext. What's the other file?

I am talking about these lines in ClassComponent specifically:

// This will check if it's a context consumer:
const context = getMaskedContext(workInProgress, unmaskedContext);
// ...
// This will do the same check:
cacheContext(workInProgress, unmaskedContext, context);

I was proposing to do something like

// Check once:
const needsContext = isContextConsumer(workInProgress);

const context = needsContext ? getMaskedContext(workInProgress, unmaskedContext) : emptyObject;
// ...
if (needsContext) {
  cacheContext(workInProgress, unmaskedContext, context);
}

Then you could remove checks in both getMaskedContext an cacheContext.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yup. Thanks for clarifying. 😄 Updated.

@bvaughn bvaughn force-pushed the improve-unmasked-context-caching branch from a06bb12 to d9f3528 Compare January 9, 2017 20:06
@@ -57,6 +57,15 @@ function getUnmaskedContext(workInProgress : Fiber) : Object {
}
exports.getUnmaskedContext = getUnmaskedContext;

function cacheContext(workInProgress : Fiber, unmaskedContext : Object, maskedContext : Object) {
const instance = workInProgress.stateNode;
if (instance) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we move this check in getMaskedContext instead?
The other caller of cacheContext always has the instance.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, we could do that. I didn't do it initially because it seemed unsafe. Fiber may not have an instance and I didn't wan to rely on external callers of this function having to know only to call this method when an instance existed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

PS. I'm happy to defer to your judgement here. You're more familiar with the project and the way things are done. Just sharing my thought process. 😄

Copy link
Collaborator

Choose a reason for hiding this comment

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

Generally I'm of the opinion that methods should be strict and either have a safety check inside (and we never do it outside) or have no safety check, and we must always do it outside.

Doing it both ways means somebody will see the "early check" pattern that was made for performance reasons and scratch their head why other calls aren't behind the same check. This will look "surprising" if one call is different from other calls in the same file. And every difference like this is mental overhead when you have to move those pieces around. Strict contracts help avoid that, and we can always fix safety concerns with tests for regressions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay. I understand your thoughts now. Thank you for elaborating.

}
}
exports.cacheContext = cacheContext;

exports.getMaskedContext = function(workInProgress : Fiber, unmaskedContext : Object) {
const type = workInProgress.type;
const contextTypes = type.contextTypes;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we remove this check here now, and always do it externally?

Copy link
Contributor Author

@bvaughn bvaughn Jan 9, 2017

Choose a reason for hiding this comment

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

We could. Same safety-related concern as above.

Edit: I think I'm going to leave this as-is since we call getMaskedContext in half a dozen places and it seems to easy to mess up if we require all callers to check isContextConsumer before calling.

Copy link
Collaborator

Choose a reason for hiding this comment

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

👍

@gaearon
Copy link
Collaborator

gaearon commented Jan 9, 2017

Looks good to me. I don't have a strong preference for the above comments, please do as it makes more sense to you.

Only store cached masked/unmasked contexts on context consumers. Move all references to __reactInternal* cached attributes inside of ReactFiberContext.
@bvaughn bvaughn force-pushed the improve-unmasked-context-caching branch from d9f3528 to e5a7b75 Compare January 9, 2017 21:51
@bvaughn bvaughn merged commit 24b8ba7 into facebook:master Jan 9, 2017
@bvaughn bvaughn deleted the improve-unmasked-context-caching branch January 9, 2017 23:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants