8000 Avoid repeated calls to getFieldDef and unnecessary immutable builders/collections by DanielThomas · Pull Request #3478 · graphql-java/graphql-java · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8000
18 changes: 15 additions & 3 deletions src/main/java/graphql/execution/ExecutionStrategy.java
Original file line number Diff line number Diff line change
Expand Up @@ -348,9 +348,9 @@ protected CompletableFuture<FieldValueInfo> resolveFieldWithInfo(ExecutionContex
new InstrumentationFieldParameters(executionContext, executionStepInfo), executionContext.getInstrumentationState()
));

CompletableFuture<FetchedValue> fetchFieldFuture = fetchField(executionContext, parameters);
CompletableFuture<FetchedValue> fetchFieldFuture = fetchField(fieldDef, executionContext, parameters);
CompletableFuture<FieldValueInfo> result = fetchFieldFuture.thenApply((fetchedValue) ->
completeField(executionContext, parameters, fetchedValue));
completeField(fieldDef, executionContext, parameters, fetchedValue));

CompletableFuture<Object> fieldValueFuture = result.thenCompose(FieldValueInfo::getFieldValueFuture);

Expand All @@ -377,7 +377,12 @@ protected CompletableFuture<FetchedValue> fetchField(ExecutionContext executionC
MergedField field = parameters.getField();
GraphQLObjectType parentType = (GraphQLObjectType) parameters.getExecutionStepInfo().getUnwrappedNonNullType();
GraphQLFieldDefinition fieldDef = getFieldDef(executionContext.getGraphQLSchema(), parentType, field.getSingleField());
GraphQLCodeRegistry codeRegistry = executionContext.getGraphQLSchema().getCodeRegistry();
return fetchField(fieldDef, executionContext, parameters);
}

private CompletableFuture<FetchedValue> fetchField(GraphQLFieldDefinition fieldDef, ExecutionContext executionContext, ExecutionStrategyParameters parameters) {
MergedField field = parameters.getField();
GraphQLObjectType parentType = (GraphQLObjectType) parameters.getExecutionStepInfo().getUnwrappedNonNullType();

Copy link
Member

Choose a reason for hiding this comment

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

The old code is left in place

  protected CompletableFuture<FetchedValue> fetchField(ExecutionContext executionContext, ExecutionStrategyParameters parameters) {
        MergedField field = parameters.getField();
        GraphQLObjectType parentType = (GraphQLObjectType) parameters.getExecutionStepInfo().getUnwrappedNonNullType();
        GraphQLFieldDefinition fieldDef = getFieldDef(executionContext.getGraphQLSchema(), parentType, field.getSingleField());
        return fetchField(fieldDef, executionContext, parameters);
    }

    private CompletableFuture<FetchedValue> fetchField(GraphQLFieldDefinition fieldDef, ExecutionContext executionContext, ExecutionStrategyParameters parameters) {

and the engine only now calls the private method here. This means we dont break backwards compatibility. So nominally a 👍

HOWEVER - we have long had the debate inside the team about JUST how much API contract we should have in the ExecutionStrategy land - eg can other compose their own engines based on using the base classes and I think the answer is... they really cant make whole new engines using this class via inheritance and hence perhaps these should not be API per say

So it might be prudent @andimarek to just change the method signature - since the old method is dangling and never called by the engine now

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, with the class marked @PublicSpi I didn't want to mess with the signatures, and three remaining references internally:

Screenshot 2024-02-28 at 5 23 59 pm

// if the DF (like PropertyDataFetcher) does not use the arguments or execution step info then dont build any

Expand Down Expand Up @@ -412,6 +417,8 @@ protected CompletableFuture<FetchedValue> fetchField(ExecutionContext executionC
.queryDirectives(queryDirectives)
.build();
});

GraphQLCodeRegistry codeRegistry = executionContext.getGraphQLSchema().getCodeRegistry();
DataFetcher<?> dataFetcher = codeRegistry.getDataFetcher(parentType, fieldDef);

Instrumentation instrumentation = executionContext.getInstrumentation();
Expand Down Expand Up @@ -555,6 +562,11 @@ protected FieldValueInfo completeField(ExecutionContext executionContext, Execut
Field field = parameters.getField().getSingleField();
GraphQLObjectType parentType = (GraphQLObjectType) parameters.getExecutionStepInfo().getUnwrappedNonNullType();
GraphQLFieldDefinition fieldDef = getFieldDef(executionContext.getGraphQLSchema(), parentType, field);
return completeField(fieldDef, executionContext, parameters, fetchedValue);
}

private FieldValueInfo completeField(GraphQLFieldDefinition fieldDef, ExecutionContext executionContext, ExecutionStrategyParameters parameters, FetchedValue fetchedValue) {
GraphQLObjectType parentType = (GraphQLObjectType) parameters.getExecutionStepInfo().getUnwrappedNonNullType();
ExecutionStepInfo executionStepInfo = createExecutionStepInfo(executionContext, parameters, fieldDef, parentType);

Instrumentation instrumentation = executionContext.getInstrumentation();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ public static class Builder {
ResultPath path = ResultPath.rootPath();
MergedField currentField;
ExecutionStrategyParameters parent;
DeferredCallContext deferredCallContext = new DeferredCallContext();
DeferredCallContext deferredCallContext;

/**
* @see ExecutionStrategyParameters#newParameters()
Expand Down Expand Up @@ -188,6 +188,9 @@ public Builder deferredCallContext(DeferredCallContext deferredCallContext) {
}

public ExecutionStrategyParameters build() {
if (deferredCallContext == null) {
deferredCallContext = new DeferredCallContext();
}
return new ExecutionStrategyParameters(executionStepInfo, source, localContext, fields, nonNullableFieldValidator, path, currentField, parent, deferredCallContext);
}
}
Expand Down
5 changes: 1 addition & 4 deletions src/main/java/graphql/execution/FieldCollector.java
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,7 @@ private void collectField(FieldCollectorParameters parameters, Map<String, Merge
.addDeferredExecution(deferredExecution))
);
} else {
fields.put(name, MergedField
.newMergedField(field)
.addDeferredExecution(deferredExecution).build()
);
fields.put(name, MergedField.newSingletonMergedField(field, deferredExecution));
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/main/java/graphql/execution/FieldValueInfo.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package graphql.execution;

import com.google.common.collect.ImmutableList;
import graphql.ExecutionResult;
import graphql.ExecutionResultImpl;
import graphql.PublicApi;
Expand Down Expand Up @@ -66,7 +67,7 @@ public String toString() {
public static class Builder {
private CompleteValueType completeValueType;
private CompletableFuture<Object> fieldValueFuture;
private List<FieldValueInfo> listInfos = new ArrayList<>();
private List<FieldValueInfo> listInfos = ImmutableList.of();
Copy link
Member

Choose a reason for hiding this comment

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

I have another PR that intends to break this class away from a builder pattern

#3465

eg JUST allocate the the object - not a builder and then the object say

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 was also going to add a SingletonMergedField but MergedField is concrete and marked as a public API, so didn't feel comfortable changing that.


public Builder(CompleteValueType completeValueType) {
this.completeValueType = completeValueType;
Expand Down
11 changes: 9 additions & 2 deletions src/main/java/graphql/execution/MergedField.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ private MergedField(ImmutableList<Field> fields, ImmutableList<DeferredExecution
this.deferredExecutions = deferredExecutions;
}

private MergedField(Field field, DeferredExecution deferredExecution) {
this(ImmutableList.of(field), deferredExecution == null ? ImmutableList.of() : ImmutableList.of(deferredExecution));
}

/**
* All merged fields have the same name.
*
Expand Down Expand Up @@ -131,11 +135,10 @@ public List<Field> getFields() {
* @return all defer executions.
*/
@ExperimentalApi
public ImmutableList<DeferredExecution> getDeferredExecutions() {
public List<DeferredExecution> getDeferredExecutions() {
return deferredExecutions;
}


public static Builder newMergedField() {
return new Builder();
}
Expand All @@ -148,6 +151,10 @@ public static Builder newMergedField(List<Field> fields) {
return new Builder().fields(fields);
}

static MergedField newSingletonMergedField(Field field, DeferredExecution deferredExecution) {
return new MergedField(field, deferredExecution);
}

public MergedField transform(Consumer<Builder> builderConsumer) {
Builder builder = new Builder(this);
builderConsumer.accept(builder);
Expand Down
12 changes: 7 additions & 5 deletions src/main/java/graphql/execution/MergedSelectionSet.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
@PublicApi
public class MergedSelectionSet {

private final ImmutableMap<String, MergedField> subFields;
private final Map<String, MergedField> subFields;
private final List<String> keys;

protected MergedSelectionSet(Map<String, MergedField> subFields) {
this.subFields = ImmutableMap.copyOf(Assert.assertNotNull(subFields));
this.subFields = subFields == null ? ImmutableMap.of() : subFields;
this.keys = ImmutableList.copyOf(this.subFields.keySet());
}

public Map<String, MergedField> getSubFields() {
Expand All @@ -40,7 +42,7 @@ public MergedField getSubField(String key) {
}

public List<String> getKeys() {
return ImmutableList.copyOf(keySet());
return keys;
}

public boolean isEmpty() {
Expand All @@ -52,10 +54,10 @@ public static Builder newMergedSelectionSet() {
}

public static class Builder {
private Map<String, MergedField> subFields = ImmutableMap.of();
Copy link
Member

Choose a reason for hiding this comment

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

ok defaulting happening in constructor 👍


private Builder() {
private Map<String, MergedField> subFields;

private Builder() {
}

public Builder subFields(Map<String, MergedField> subFields) {
Expand Down
19 changes: 7 additions & 12 deletions src/main/java/graphql/schema/GraphQLInterfaceType.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

import com.google.common.collect.ImmutableList;
import graphql.Assert;
import graphql.AssertException;
import graphql.DirectivesUtil;
import graphql.Internal;
import graphql.PublicApi;
import graphql.language.InterfaceTypeDefinition;
import graphql.language.InterfaceTypeExtensionDefinition;
import graphql.util.FpKit;
import graphql.util.TraversalControl;
import graphql.util.TraverserContext;

Expand All @@ -20,12 +20,12 @@
import java.util.function.UnaryOperator;

import static graphql.Assert.assertNotNull;
import static graphql.Assert.assertShouldNeverHappen;
import static graphql.Assert.assertValidName;
import static graphql.collect.ImmutableKit.emptyList;
import static graphql.schema.GraphqlTypeComparators.sortTypes;
import static graphql.util.FpKit.getByName;
import static graphql.util.FpKit.valuesToList;
import static java.lang.String.format;

/**
* In graphql, an interface is an abstract type that defines the set of fields that a type must include to
Expand All @@ -41,7 +41,7 @@ public class GraphQLInterfaceType implements GraphQLNamedType, GraphQLCompositeT

private final String name;
private final String description;
private final Map<String, GraphQLFieldDefinition> fieldDefinitionsByName = new LinkedHashMap<>();
private final Map<String, GraphQLFieldDefinition> fieldDefinitionsByName;
private final TypeResolver typeResolver;
private final InterfaceTypeDefinition definition;
private final ImmutableList<InterfaceTypeExtensionDefinition> extensionDefinitions;
Expand Down Expand Up @@ -78,17 +78,12 @@ private GraphQLInterfaceType(String name,
this.originalInterfaces = ImmutableList.copyOf(sortTypes(interfaceComparator, interfaces));
this.extensionDefinitions = ImmutableList.copyOf(extensionDefinitions);
this.directivesHolder = new DirectivesUtil.DirectivesHolder(directives, appliedDirectives);
buildDefinitionMap(fieldDefinitions);
this.fieldDefinitionsByName = buildDefinitionMap(fieldDefinitions);
}

private void buildDefinitionMap(List<GraphQLFieldDefinition> fieldDefinitions) {
for (GraphQLFieldDefinition fieldDefinition : fieldDefinitions) {
String name = fieldDefinition.getName();
if (fieldDefinitionsByName.containsKey(name)) {
throw new AssertException(format("Duplicated definition for field '%s' in interface '%s'", name, this.name));
}
fieldDefinitionsByName.put(name, fieldDefinition);
}
private Map<String, GraphQLFieldDefinition> buildDefinitionMap(List<GraphQLFieldDefinition> fieldDefinitions) {
return FpKit.getByName(fieldDefinitions, GraphQLFieldDefinition::getName,
(fld1, fld2) -> assertShouldNeverHappen("Duplicated definition for field '%s' in interface '%s'", fld1.getName(), this.name));
}

@Override
Expand Down
8 changes: 4 additions & 4 deletions src/main/java/graphql/schema/GraphQLObjectType.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public class GraphQLObjectType implements GraphQLNamedOutputType, GraphQLComposi
private final String name;
private final String description;
private final Comparator<? super GraphQLSchemaElement> interfaceComparator;
private final ImmutableMap<String, GraphQLFieldDefinition> fieldDefinitionsByName;
private final Map<String, GraphQLFieldDefinition> fieldDefinitionsByName;
private final ImmutableList<GraphQLNamedOutputType> originalInterfaces;
private final DirectivesUtil.DirectivesHolder directivesHolder;
private final ObjectTypeDefinition definition;
Expand Down Expand Up @@ -82,9 +82,9 @@ void replaceInterfaces(List<GraphQLNamedOutputType> interfaces) {
this.replacedInterfaces = ImmutableList.copyOf(sortTypes(interfaceComparator, interfaces));
}

private ImmutableMap<String, GraphQLFieldDefinition> buildDefinitionMap(List<GraphQLFieldDefinition> fieldDefinitions) {
return ImmutableMap.copyOf(FpKit.getByName(fieldDefinitions, GraphQLFieldDefinition::getName,
(fld1, fld2) -> assertShouldNeverHappen("Duplicated definition for field '%s' in type '%s'", fld1.getName(), this.name)));
private Map<String, GraphQLFieldDefinition> buildDefinitionMap(List<GraphQLFieldDefinition> fieldDefinitions) {
return FpKit.getByName(fieldDefinitions, GraphQLFieldDefinition::getName,
(fld1, fld2) -> assertShouldNeverHappen("Duplicated definition for field '%s' in type '%s'", fld1.getName(), this.name));
Copy link
Member

Choose a reason for hiding this comment

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

yup - just checked - this does not change the immutability of what we give out since we never give out the map of fields directly

Copy link
Member

Choose a reason for hiding this comment

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

That said building object types is done once per schema - so this really wont affect runtime query performance at all I think - or is ImmutableMap lookup of fields somehow slower ???

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, HashMap and therefore LinkedHashMap compares hashes and identity equality first, so is particularly suited for String keys.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, I should add, this matters less than when I started working on this, because I found I could reduce the calls from 3 to 1, but it'll still help.

}

@Override
Expand Down
16 changes: 13 additions & 3 deletions src/test/java/benchmark/TwitterBenchmark.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -128,8 +132,14 @@ protected Optional<Object> getPersistedQueryId(ExecutionInput executionInput) {
}
}

public static void main(String[] args) {
ExecutionResult result = execute();
System.out.println(result);
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include("benchmark.TwitterBenchmark")
.forks(1)
.warmupIterations(5)
.measurementIterations(10)
.build();

new Runner(opt).run();
Copy link
Member

Choose a reason for hiding this comment

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

question - I noticed you put main() methods for running the JMH code. Are you some how running this in a standard way (say on a controlled spec machine say?) ??

I ask because we have a desire to do this some how (we worked with Twitter on this until Elon Husk wrecked the joint)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This makes it easier to iterate from the IDE and profile, setting the forks to 0.

}
}
0