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 caching of anonymous types #18231

Merged
merged 5 commits into from
Sep 5, 2017
Merged

Improve caching of anonymous types #18231

merged 5 commits into from
Sep 5, 2017

Conversation

ahejlsberg
Copy link
Member

@ahejlsberg ahejlsberg commented Sep 3, 2017

We have recently seen a number of out-of-memory issues that are caused by runaway anonymous object type instantiations and comparisons. This PR introduces a new and much improved caching scheme for such types. The key idea is to track which type variables are in scope at the source origin of each anonymous object type and cache instantiations of the type based on the actual type arguments for those type variables. This means anonymous object type instantiation caching becomes as efficient as that for classes and interfaces, and out-of-memory issues that were previously fixed by switching from type aliases to interfaces should now be gone.

The RWC tests all pass. Only changes are some missing elaborations in error messages that result from improved sharing of identical types.

Fixes #16511.
Fixes #17036.
Fixes #17968.

@sandersn
Copy link
Member

sandersn commented Sep 5, 2017

We should get some memory numbers for large or medium projects that don't benefit from this (if there are there are projects like this) to make sure there's not too much overhead.

Copy link
Member

@sandersn sandersn left a comment

Choose a reason for hiding this comment

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

I have a couple of questions below.

@@ -3469,8 +3468,6 @@ namespace ts {
/* @internal */
export interface TypeMapper {
Copy link
Member

Choose a reason for hiding this comment

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

with this change, it would now be easier to read as type TypeMapper = (t: TypeParameter) => Type;

if (!typeParameters) {
// The first time an anonymous type is instantiated we compute and store a list of the type
// parameters that are in scope (and therefore potentially referenced). For type literals that
// aren't the right hand side of a generic type alias declaration we optimize by reducing the
Copy link
Member

Choose a reason for hiding this comment

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

should be "the rhs of a non-generic type alias declaration", right?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, we optimize for type literals, except when they are the RHS of a generic type alias. For those we want to preserve the distinct instantiations even when they don't reference the type parameters (for fidelity when displaying types).

return node.kind === SyntaxKind.ThisType || forEachChild(node, checkThis);
}
function checkIdentifier(node: Node): boolean {
return node.kind === SyntaxKind.Identifier && isPartOfTypeNode(node) && getTypeFromTypeNode(<TypeNode>node) === tp || forEachChild(node, checkIdentifier);
Copy link
Member

Choose a reason for hiding this comment

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

This call to getTypeFromTypeNode is likely to 'pull' a lot of types that might otherwise never be instantiated themselves. It's probably nothing because, during instantiation, we'll surely be instantiating nearby types anyway, but worth thinking about nonetheless.

Copy link
Member Author

Choose a reason for hiding this comment

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

No, it is always fine to call getTypeFromTypeNode. It will be called by the checker anyways when as we type check a program.

links.typeParameters = typeParameters;
if (typeParameters.length) {
links.instantiations = createMap<Type>();
links.instantiations.set(getTypeListId(typeParameters), target);
Copy link
Member

Choose a reason for hiding this comment

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

I don't understand how caching the uninstantiated type helps.

Copy link
Member Author

Choose a reason for hiding this comment

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

The uninstantiated type is the same as an instantiation where each type parameter has itself as its type argument. No point in recomputing that instantiation, we already have it.

case SyntaxKind.TypeAliasDeclaration:
case SyntaxKind.JSDocTemplateTag:
case SyntaxKind.MappedType:
const outerTypeParameters = getOuterTypeParameters(node, includeThisTypes);
Copy link
Member

Choose a reason for hiding this comment

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

Why is this line needed? Aren't further iterations of the while loop equivalent to calling getOuterTypeParameters recursively?

Copy link
Member

Choose a reason for hiding this comment

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

Oh, because we always return whenever any of these cases is true. Why not append to a local list, break, and keep looping instead of recurring and returning? Seems like it would be more efficient (as long as the final result isn't order-dependent, but that could be fixed by swapping the order in calls to append).

Copy link
Member Author

Choose a reason for hiding this comment

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

Our use of the getOuterTypeParameters function in code for classes and interfaces requires the list to be in outside-in order. Hence the recursion. We could insert new elements at the beginning of the list or reverse the list at the end instead, but neither are better solutions.

@sandersn
Copy link
Member

sandersn commented Sep 5, 2017

Also fixes the crash in #17097 found by @mulyoved when using glamorous, which used the complicated Omit mapped type.

@ahejlsberg
Copy link
Member Author

@sandersn Regarding memory usage, it varies from no change to massive savings, but I haven't seen meaningfully increased consumption anywhere. There is no significant change for the Monaco and TFS code bases, for example.

@ahejlsberg ahejlsberg merged commit 6c8bc18 into master Sep 5, 2017
@ahejlsberg ahejlsberg deleted the cacheAnonymousTypes branch September 5, 2017 20:11
@microsoft microsoft locked and limited conversation to collaborators Jun 14, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
3 participants