-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Some benchmarking and improvements to schema creation times and object allocations #2453
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
Changes from all commits
d58a463
f65469e
986a645
c3f6713
56275e9
c7511b8
f4d9331
ccd9260
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| package graphql.schema; | ||
|
|
||
| import graphql.Assert; | ||
| import graphql.Internal; | ||
| import graphql.PublicApi; | ||
| import graphql.schema.visibility.GraphqlFieldVisibility; | ||
|
|
||
|
|
@@ -189,7 +190,7 @@ public static class Builder { | |
| private final Map<String, TypeResolver> typeResolverMap = new HashMap<>(); | ||
| private GraphqlFieldVisibility fieldVisibility = DEFAULT_FIELD_VISIBILITY; | ||
| private DataFetcherFactory<?> defaultDataFetcherFactory = env -> PropertyDataFetcher.fetching(env.getFieldDefinition().getName()); | ||
|
|
||
| private boolean changed = false; | ||
|
|
||
| private Builder() { | ||
| } | ||
|
|
@@ -202,6 +203,38 @@ private Builder(GraphQLCodeRegistry codeRegistry) { | |
| this.defaultDataFetcherFactory = codeRegistry.defaultDataFetcherFactory; | ||
| } | ||
|
|
||
| /** | ||
| * A helper method to track if the builder changes from the point | ||
| * at which this method was called. | ||
| * | ||
| * @return this builder for fluent code | ||
| */ | ||
| @Internal | ||
| public Builder trackChanges() { | ||
| changed = false; | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * @return true if the builder has changed since {@link #trackChanges()} was called | ||
| */ | ||
| @Internal | ||
| public boolean hasChanged() { | ||
| return changed; | ||
| } | ||
|
|
||
| private Builder markChanged() { | ||
| changed = true; | ||
| return this; | ||
| } | ||
|
|
||
| private Builder markChanged(boolean condition) { | ||
| if (condition) { | ||
| changed = true; | ||
| } | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Returns a data fetcher associated with a field within a container type | ||
| * | ||
|
|
@@ -309,7 +342,7 @@ public Builder systemDataFetcher(FieldCoordinates coordinates, DataFetcher<?> da | |
| assertNotNull(coordinates); | ||
| coordinates.assertValidNames(); | ||
| systemDataFetcherMap.put(coordinates.getFieldName(), DataFetcherFactories.useDataFetcher(dataFetcher)); | ||
| return this; | ||
| return markChanged(); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -329,7 +362,7 @@ public Builder dataFetcher(FieldCoordinates coordinates, DataFetcherFactory<?> d | |
| } else { | ||
| dataFetcherMap.put(coordinates, dataFetcherFactory); | ||
| } | ||
| return this; | ||
| return markChanged(); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -347,6 +380,7 @@ public Builder dataFetcherIfAbsent(FieldCoordinates coordinates, DataFetcher<?> | |
| } else { | ||
| dataFetcher(coordinates, dataFetcher); | ||
| } | ||
| return markChanged(); | ||
| } | ||
| return this; | ||
| } | ||
|
|
@@ -362,7 +396,7 @@ public Builder dataFetcherIfAbsent(FieldCoordinates coordinates, DataFetcher<?> | |
| public Builder dataFetchers(String parentTypeName, Map<String, DataFetcher<?>> fieldDataFetchers) { | ||
| assertNotNull(fieldDataFetchers); | ||
| fieldDataFetchers.forEach((fieldName, dataFetcher) -> dataFetcher(coordinates(parentTypeName, fieldName), dataFetcher)); | ||
| return this; | ||
| return markChanged(!fieldDataFetchers.isEmpty()); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -375,57 +409,63 @@ public Builder dataFetchers(String parentTypeName, Map<String, DataFetcher<?>> f | |
| */ | ||
| public Builder defaultDataFetcher(DataFetcherFactory<?> defaultDataFetcherFactory) { | ||
| this.defaultDataFetcherFactory = Assert.assertNotNull(defaultDataFetcherFactory); | ||
| return this; | ||
| return markChanged(); | ||
| } | ||
|
|
||
| public Builder dataFetchers(GraphQLCodeRegistry codeRegistry) { | ||
| this.dataFetcherMap.putAll(codeRegistry.dataFetcherMap); | ||
| return this; | ||
| return markChanged(!codeRegistry.dataFetcherMap.isEmpty()); | ||
| } | ||
|
|
||
| public Builder typeResolver(GraphQLInterfaceType interfaceType, TypeResolver typeResolver) { | ||
| typeResolverMap.put(interfaceType.getName(), typeResolver); | ||
| return this; | ||
| return markChanged(); | ||
| } | ||
|
|
||
| public Builder typeResolverIfAbsent(GraphQLInterfaceType interfaceType, TypeResolver typeResolver) { | ||
| typeResolverMap.putIfAbsent(interfaceType.getName(), typeResolver); | ||
| if (!typeResolverMap.containsKey(interfaceType.getName())) { | ||
| typeResolverMap.put(interfaceType.getName(), typeResolver); | ||
| return markChanged(); | ||
| } | ||
| return this; | ||
| } | ||
|
|
||
| public Builder typeResolver(GraphQLUnionType unionType, TypeResolver typeResolver) { | ||
| typeResolverMap.put(unionType.getName(), typeResolver); | ||
| return this; | ||
| return markChanged(); | ||
| } | ||
|
|
||
| public Builder typeResolverIfAbsent(GraphQLUnionType unionType, TypeResolver typeResolver) { | ||
| typeResolverMap.putIfAbsent(unionType.getName(), typeResolver); | ||
| return this; | ||
| if (!typeResolverMap.containsKey(unionType.getName())) { | ||
| typeResolverMap.put(unionType.getName(), typeResolver); | ||
| return markChanged(); | ||
| } | ||
| return markChanged(); | ||
| } | ||
|
|
||
| public Builder typeResolver(String typeName, TypeResolver typeResolver) { | ||
| typeResolverMap.put(assertValidName(typeName), typeResolver); | ||
| return this; | ||
| return markChanged(); | ||
| } | ||
|
|
||
| public Builder typeResolvers(GraphQLCodeRegistry codeRegistry) { | ||
| this.typeResolverMap.putAll(codeRegistry.typeResolverMap); | ||
| return this; | ||
| return markChanged(!codeRegistry.typeResolverMap.isEmpty()); | ||
| } | ||
|
|
||
| public Builder fieldVisibility(GraphqlFieldVisibility fieldVisibility) { | ||
| this.fieldVisibility = assertNotNull(fieldVisibility); | ||
| return this; | ||
| return markChanged(); | ||
| } | ||
|
|
||
| public Builder clearDataFetchers() { | ||
| dataFetcherMap.clear(); | ||
| return this; | ||
| return markChanged(); | ||
| } | ||
|
|
||
| public Builder clearTypeResolvers() { | ||
| typeResolverMap.clear(); | ||
| return this; | ||
| return markChanged(); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All the mutative methods mark the builder as changed |
||
| } | ||
|
|
||
| public GraphQLCodeRegistry build() { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -140,7 +140,7 @@ private Object transformImpl(final GraphQLSchema schema, GraphQLSchemaElement sc | |
| final Map<String, GraphQLTypeReference> typeReferences = new LinkedHashMap<>(); | ||
|
|
||
| // first pass - general transformation | ||
| traverseAndTransform(dummyRoot, changedTypes, typeReferences, visitor, codeRegistry); | ||
| boolean schemaChanged = traverseAndTransform(dummyRoot, changedTypes, typeReferences, visitor, codeRegistry); | ||
|
|
||
| // if we have changed any named elements AND we have type references referring to them then | ||
| // we need to make a second pass to replace these type references to the new names | ||
|
|
@@ -152,9 +152,13 @@ private Object transformImpl(final GraphQLSchema schema, GraphQLSchemaElement sc | |
| } | ||
|
|
||
| if (schema != null) { | ||
| GraphQLSchema graphQLSchema = dummyRoot.rebuildSchema(codeRegistry); | ||
| if (postTransformation != null) { | ||
| graphQLSchema = graphQLSchema.transform(postTransformation); | ||
|
|
||
| GraphQLSchema graphQLSchema = schema; | ||
| if (schemaChanged || codeRegistry.hasChanged()) { | ||
| graphQLSchema = dummyRoot.rebuildSchema(codeRegistry); | ||
| if (postTransformation != null) { | ||
| graphQLSchema = graphQLSchema.transform(postTransformation); | ||
| } | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a BIG change - if the visitor didnt change anything and the code registry didn't change - then nothing has changed - its the same schema - so we return it direct |
||
| } | ||
| return graphQLSchema; | ||
| } else { | ||
|
|
@@ -177,7 +181,7 @@ public TraversalControl visitGraphQLTypeReference(GraphQLTypeReference typeRef, | |
| traverseAndTransform(dummyRoot, new HashMap<>(), new HashMap<>(), typeRefVisitor, codeRegistry); | ||
| } | ||
|
|
||
| private void traverseAndTransform(DummyRoot dummyRoot, Map<String, GraphQLNamedType> changedTypes, Map<String, GraphQLTypeReference> typeReferences, GraphQLTypeVisitor visitor, GraphQLCodeRegistry.Builder codeRegistry) { | ||
| private boolean traverseAndTransform(DummyRoot dummyRoot, Map<String, GraphQLNamedType> changedTypes, Map<String, GraphQLTypeReference> typeReferences, GraphQLTypeVisitor visitor, GraphQLCodeRegistry.Builder codeRegistry) { | ||
| List<NodeZipper<GraphQLSchemaElement>> zippers = new LinkedList<>(); | ||
| Map<GraphQLSchemaElement, NodeZipper<GraphQLSchemaElement>> zipperByNodeAfterTraversing = new LinkedHashMap<>(); | ||
| Map<GraphQLSchemaElement, NodeZipper<GraphQLSchemaElement>> zipperByOriginalNode = new LinkedHashMap<>(); | ||
|
|
@@ -268,16 +272,16 @@ public TraversalControl backRef(TraverserContext<GraphQLSchemaElement> context) | |
|
|
||
| List<List<GraphQLSchemaElement>> stronglyConnectedTopologicallySorted = getStronglyConnectedComponentsTopologicallySorted(reverseDependencies, typeRefReverseDependencies); | ||
|
|
||
| zipUpToDummyRoot(zippers, stronglyConnectedTopologicallySorted, breadcrumbsByZipper, zipperByNodeAfterTraversing); | ||
| return zipUpToDummyRoot(zippers, stronglyConnectedTopologicallySorted, breadcrumbsByZipper, zipperByNodeAfterTraversing); | ||
| } | ||
|
|
||
|
|
||
| private void zipUpToDummyRoot(List<NodeZipper<GraphQLSchemaElement>> zippers, | ||
| List<List<GraphQLSchemaElement>> stronglyConnectedTopologicallySorted, | ||
| Map<NodeZipper<GraphQLSchemaElement>, List<List<Breadcrumb<GraphQLSchemaElement>>>> breadcrumbsByZipper, | ||
| Map<GraphQLSchemaElement, NodeZipper<GraphQLSchemaElement>> nodeToZipper) { | ||
| private boolean zipUpToDummyRoot(List<NodeZipper<GraphQLSchemaElement>> zippers, | ||
| List<List<GraphQLSchemaElement>> stronglyConnectedTopologicallySorted, | ||
| Map<NodeZipper<GraphQLSchemaElement>, List<List<Breadcrumb<GraphQLSchemaElement>>>> breadcrumbsByZipper, | ||
| Map<GraphQLSchemaElement, NodeZipper<GraphQLSchemaElement>> nodeToZipper) { | ||
| if (zippers.size() == 0) { | ||
| return; | ||
| return false; | ||
| } | ||
| Set<NodeZipper<GraphQLSchemaElement>> curZippers = new LinkedHashSet<>(zippers); | ||
|
|
||
|
|
@@ -331,8 +335,8 @@ private void zipUpToDummyRoot(List<NodeZipper<GraphQLSchemaElement>> zippers, | |
| breadcrumbsByZipper.put(newZipper, breadcrumbsForOriginalParent); | ||
|
|
||
| } | ||
|
|
||
| } | ||
| return true; | ||
| } | ||
|
|
||
| private Map<NodeZipper<GraphQLSchemaElement>, List<Breadcrumb<GraphQLSchemaElement>>> zipperWithSameParent(GraphQLSchemaElement parent, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,23 +43,35 @@ public SchemaDirectiveWiringSchemaGeneratorPostProcessing(TypeDefinitionRegistry | |
|
|
||
| @Override | ||
| public GraphQLSchema process(GraphQLSchema originalSchema) { | ||
| GraphQLSchema newSchema = SchemaTransformer.transformSchema(originalSchema, new Visitor()); | ||
| return newSchema.transform(builder -> { | ||
| // they could have changed the code registry so rebuild it | ||
| GraphQLCodeRegistry codeRegistry = this.codeRegistryBuilder.build(); | ||
| builder.codeRegistry(codeRegistry); | ||
| }); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have two schema transformers here - one for a visitor that might not have done anything And one because they MAY have changed the code registry. Another way to fix this would be to have a way to change a schema's code registry safely (since its really a object swap in not a tree rebuild - but thats not this does
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This turned up in JProfiler as a heavily invoked bit of code in schema builds |
||
| codeRegistryBuilder.trackChanges(); | ||
| Visitor visitor = new Visitor(); | ||
| GraphQLSchema newSchema = SchemaTransformer.transformSchema(originalSchema, visitor); | ||
| if (visitor.schemaChanged() || codeRegistryBuilder.hasChanged()) { | ||
| return newSchema.transform(builder -> { | ||
| // they could have changed the code registry so rebuild it | ||
| GraphQLCodeRegistry codeRegistry = this.codeRegistryBuilder.build(); | ||
| builder.codeRegistry(codeRegistry); | ||
| }); | ||
| } | ||
| return newSchema; | ||
| } | ||
|
|
||
| public class Visitor extends GraphQLTypeVisitorStub { | ||
|
|
||
| private boolean schemaChanged = false; | ||
|
|
||
| public boolean schemaChanged() { | ||
| return schemaChanged; | ||
| } | ||
|
|
||
| private SchemaGeneratorDirectiveHelper.Parameters mkBehaviourParams() { | ||
| return new SchemaGeneratorDirectiveHelper.Parameters(typeRegistry, runtimeWiring, directiveBehaviourContext, codeRegistryBuilder); | ||
| } | ||
|
|
||
| private TraversalControl changOrContinue(GraphQLSchemaElement node, GraphQLSchemaElement newNode, TraverserContext<GraphQLSchemaElement> context) { | ||
| if (node != newNode) { | ||
| TreeTransformerUtil.changeNode(context, newNode); | ||
| schemaChanged = true; | ||
| } | ||
| return CONTINUE; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -56,10 +56,11 @@ public DefaultTraverserContext(T curNode, | |
| if (parent == null || parent.isRootContext()) { | ||
| this.breadcrumbs = ImmutableKit.emptyList(); | ||
| } else { | ||
| List<Breadcrumb<T>> breadcrumbs = new ArrayList<>(parent.getBreadcrumbs().size() + 1); | ||
| breadcrumbs.add(new Breadcrumb<>(this.parent.thisNode(), this.location)); | ||
| breadcrumbs.addAll(parent.getBreadcrumbs()); | ||
| this.breadcrumbs = ImmutableList.copyOf(breadcrumbs); | ||
| this.breadcrumbs = ImmutableList.<Breadcrumb<T>>builderWithExpectedSize(parent.getBreadcrumbs().size() + 1) | ||
| .add(new Breadcrumb<>(this.parent.thisNode(), this.location)) | ||
| .addAll(parent.getBreadcrumbs()) | ||
| .build(); | ||
|
|
||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is more efficient in memory allocation and faster to boot |
||
| } | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This allows us to mark a code registry and know if it changes since we marked it.