From 29f82204a5ba3c70264b0a38cf42aa11a0fd37ad Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Thu, 15 May 2025 16:54:43 +1000 Subject: [PATCH] Cherry pick 3934 for execution step info builder changes --- .../execution/ExecutionStepInfoBenchmark.java | 86 +++++++++++++++++++ .../graphql/execution/ExecutionStepInfo.java | 35 +++++++- .../execution/ExecutionStepInfoFactory.java | 81 ++++++++++++++++- .../graphql/execution/ExecutionStrategy.java | 51 +---------- 4 files changed, 199 insertions(+), 54 deletions(-) create mode 100644 src/jmh/java/graphql/execution/ExecutionStepInfoBenchmark.java diff --git a/src/jmh/java/graphql/execution/ExecutionStepInfoBenchmark.java b/src/jmh/java/graphql/execution/ExecutionStepInfoBenchmark.java new file mode 100644 index 000000000..e2678c67f --- /dev/null +++ b/src/jmh/java/graphql/execution/ExecutionStepInfoBenchmark.java @@ -0,0 +1,86 @@ +package graphql.execution; + +import graphql.Scalars; +import graphql.language.Field; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; +import org.openjdk.jmh.profile.GCProfiler; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +import java.util.concurrent.TimeUnit; + +import static graphql.execution.ExecutionStepInfo.newExecutionStepInfo; + +@State(Scope.Benchmark) +@Warmup(iterations = 2, time = 5) +@Measurement(iterations = 2) +@Fork(2) +public class ExecutionStepInfoBenchmark { + @Param({"1000000", "2000000"}) + int howManyItems = 1000000; + + @Setup(Level.Trial) + public void setUp() { + } + + @TearDown(Level.Trial) + public void tearDown() { + } + + + MergedField mergedField = MergedField.newMergedField().addField(Field.newField("f").build()).build(); + + ResultPath path = ResultPath.rootPath().segment("f"); + ExecutionStepInfo rootStepInfo = newExecutionStepInfo() + .path(path).type(Scalars.GraphQLString) + .field(mergedField) + .build(); + + + @Benchmark + @BenchmarkMode(Mode.Throughput) + @OutputTimeUnit(TimeUnit.SECONDS) + public void benchMarkDirectConstructorThroughput(Blackhole blackhole) { + for (int i = 0; i < howManyItems; i++) { + ResultPath newPath = path.segment(1); + ExecutionStepInfo newOne = rootStepInfo.transform(Scalars.GraphQLInt, rootStepInfo, newPath); + blackhole.consume(newOne); + } + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + @OutputTimeUnit(TimeUnit.SECONDS) + public void benchMarkBuilderThroughput(Blackhole blackhole) { + for (int i = 0; i < howManyItems; i++) { + ResultPath newPath = path.segment(1); + ExecutionStepInfo newOne = newExecutionStepInfo(rootStepInfo).parentInfo(rootStepInfo) + .type(Scalars.GraphQLInt).path(newPath).build(); + blackhole.consume(newOne); + } + } + + public static void main(String[] args) throws Exception { + Options opt = new OptionsBuilder() + .include("graphql.execution.ExecutionStepInfoBenchmark") + .addProfiler(GCProfiler.class) + .build(); + + new Runner(opt).run(); + } + +} diff --git a/src/main/java/graphql/execution/ExecutionStepInfo.java b/src/main/java/graphql/execution/ExecutionStepInfo.java index 6ecf42c5f..eefa8a81c 100644 --- a/src/main/java/graphql/execution/ExecutionStepInfo.java +++ b/src/main/java/graphql/execution/ExecutionStepInfo.java @@ -1,5 +1,6 @@ package graphql.execution; +import graphql.Internal; import graphql.PublicApi; import graphql.collect.ImmutableMapWithNullValues; import graphql.schema.GraphQLFieldDefinition; @@ -77,6 +78,25 @@ private ExecutionStepInfo(Builder builder) { this.fieldContainer = builder.fieldContainer; } + /* + * This constructor allows for a slightly ( 1% ish) faster transformation without an intermediate Builder object + */ + private ExecutionStepInfo(GraphQLOutputType type, + ResultPath path, + ExecutionStepInfo parent, + MergedField field, + GraphQLFieldDefinition fieldDefinition, + GraphQLObjectType fieldContainer, + Supplier> arguments) { + this.type = assertNotNull(type, () -> "you must provide a graphql type"); + this.path = path; + this.parent = parent; + this.field = field; + this.fieldDefinition = fieldDefinition; + this.fieldContainer = fieldContainer; + this.arguments = arguments; + } + /** * The GraphQLObjectType where fieldDefinition is defined. * Note: @@ -193,13 +213,12 @@ public boolean hasParent() { public ExecutionStepInfo changeTypeWithPreservedNonNull(GraphQLOutputType newType) { assertTrue(!GraphQLTypeUtil.isNonNull(newType), () -> "newType can't be non null"); if (isNonNullType()) { - return newExecutionStepInfo(this).type(GraphQLNonNull.nonNull(newType)).build(); + return transform(GraphQLNonNull.nonNull(newType)); } else { - return newExecutionStepInfo(this).type(newType).build(); + return transform(newType); } } - /** * @return the type in graphql SDL format, eg [typeName!]! */ @@ -216,6 +235,16 @@ public String toString() { '}'; } + @Internal + ExecutionStepInfo transform(GraphQLOutputType type) { + return new ExecutionStepInfo(type, path, parent, field, fieldDefinition, fieldContainer, arguments); + } + + @Internal + ExecutionStepInfo transform(GraphQLOutputType type, ExecutionStepInfo parent, ResultPath path) { + return new ExecutionStepInfo(type, path, parent, field, fieldDefinition, fieldContainer, arguments); + } + public ExecutionStepInfo transform(Consumer builderConsumer) { Builder builder = new Builder(this); builderConsumer.accept(builder); diff --git a/src/main/java/graphql/execution/ExecutionStepInfoFactory.java b/src/main/java/graphql/execution/ExecutionStepInfoFactory.java index 1a9f91aa4..ec2716aec 100644 --- a/src/main/java/graphql/execution/ExecutionStepInfoFactory.java +++ b/src/main/java/graphql/execution/ExecutionStepInfoFactory.java @@ -1,19 +1,92 @@ package graphql.execution; import graphql.Internal; +import graphql.collect.ImmutableMapWithNullValues; +import graphql.language.Argument; +import graphql.schema.GraphQLArgument; +import graphql.schema.GraphQLCodeRegistry; +import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLList; +import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLOutputType; +import graphql.util.FpKit; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +import static graphql.execution.ExecutionStepInfo.newExecutionStepInfo; @Internal +@NullMarked public class ExecutionStepInfoFactory { public ExecutionStepInfo newExecutionStepInfoForListElement(ExecutionStepInfo executionInfo, ResultPath indexedPath) { GraphQLList fieldType = (GraphQLList) executionInfo.getUnwrappedNonNullType(); GraphQLOutputType typeInList = (GraphQLOutputType) fieldType.getWrappedType(); - return executionInfo.transform(builder -> builder - .parentInfo(executionInfo) - .type(typeInList) - .path(indexedPath)); + return executionInfo.transform(typeInList, executionInfo, indexedPath); + } + + /** + * Builds the type info hierarchy for the current field + * + * @param executionContext the execution context in play + * @param parameters contains the parameters holding the fields to be executed and source object + * @param fieldDefinition the field definition to build type info for + * @param fieldContainer the field container + * + * @return a new type info + */ + public ExecutionStepInfo createExecutionStepInfo(ExecutionContext executionContext, + ExecutionStrategyParameters parameters, + GraphQLFieldDefinition fieldDefinition, + @Nullable GraphQLObjectType fieldContainer) { + MergedField field = parameters.getField(); + ExecutionStepInfo parentStepInfo = parameters.getExecutionStepInfo(); + GraphQLOutputType fieldType = fieldDefinition.getType(); + List fieldArgDefs = fieldDefinition.getArguments(); + Supplier> argumentValues = ImmutableMapWithNullValues::emptyMap; + // + // no need to create args at all if there are none on the field def + // + if (!fieldArgDefs.isEmpty()) { + argumentValues = getArgumentValues(executionContext, fieldArgDefs, field.getArguments()); + } + + + return newExecutionStepInfo() + .type(fieldType) + .fieldDefinition(fieldDefinition) + .fieldContainer(fieldContainer) + .field(field) + .path(parameters.getPath()) + .parentInfo(parentStepInfo) + .arguments(argumentValues) + .build(); } + @NonNull + private static Supplier> getArgumentValues(ExecutionContext executionContext, + List fieldArgDefs, + List fieldArgs) { + Supplier> argumentValues; + GraphQLCodeRegistry codeRegistry = executionContext.getGraphQLSchema().getCodeRegistry(); + Supplier> argValuesSupplier = () -> { + Map resolvedValues = ValuesResolver.getArgumentValues(codeRegistry, + fieldArgDefs, + fieldArgs, + executionContext.getCoercedVariables(), + executionContext.getGraphQLContext(), + executionContext.getLocale()); + + return ImmutableMapWithNullValues.copyOf(resolvedValues); + }; + argumentValues = FpKit.intraThreadMemoize(argValuesSupplier); + return argumentValues; + } + + } diff --git a/src/main/java/graphql/execution/ExecutionStrategy.java b/src/main/java/graphql/execution/ExecutionStrategy.java index ea6b77b83..05bd2effb 100644 --- a/src/main/java/graphql/execution/ExecutionStrategy.java +++ b/src/main/java/graphql/execution/ExecutionStrategy.java @@ -14,7 +14,6 @@ import graphql.TrivialDataFetcher; import graphql.TypeMismatchError; import graphql.UnresolvedTypeError; -import graphql.collect.ImmutableMapWithNullValues; import graphql.execution.directives.QueryDirectives; import graphql.execution.directives.QueryDirectivesImpl; import graphql.execution.incremental.DeferredExecutionSupport; @@ -29,7 +28,6 @@ import graphql.execution.reactive.ReactiveSupport; import graphql.extensions.ExtensionsBuilder; import graphql.introspection.Introspection; -import graphql.language.Argument; import graphql.language.Field; import graphql.normalized.ExecutableNormalizedField; import graphql.normalized.ExecutableNormalizedOperation; @@ -38,12 +36,10 @@ import graphql.schema.DataFetchingEnvironment; import graphql.schema.DataFetchingFieldSelectionSet; import graphql.schema.DataFetchingFieldSelectionSetImpl; -import graphql.schema.GraphQLArgument; import graphql.schema.GraphQLCodeRegistry; import graphql.schema.GraphQLEnumType; import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLObjectType; -import graphql.schema.GraphQLOutputType; import graphql.schema.GraphQLScalarType; import graphql.schema.GraphQLSchema; import graphql.schema.GraphQLType; @@ -64,7 +60,6 @@ import java.util.function.Supplier; import static graphql.execution.Async.exceptionallyCompletedFuture; -import static graphql.execution.ExecutionStepInfo.newExecutionStepInfo; import static graphql.execution.FieldCollectorParameters.newParameters; import static graphql.execution.FieldValueInfo.CompleteValueType.ENUM; import static graphql.execution.FieldValueInfo.CompleteValueType.LIST; @@ -1094,48 +1089,10 @@ protected ExecutionStepInfo createExecutionStepInfo(ExecutionContext executionCo ExecutionStrategyParameters parameters, GraphQLFieldDefinition fieldDefinition, GraphQLObjectType fieldContainer) { - MergedField field = parameters.getField(); - ExecutionStepInfo parentStepInfo = parameters.getExecutionStepInfo(); - GraphQLOutputType fieldType = fieldDefinition.getType(); - List fieldArgDefs = fieldDefinition.getArguments(); - Supplier> argumentValues = ImmutableMapWithNullValues::emptyMap; - // - // no need to create args at all if there are none on the field def - // - if (!fieldArgDefs.isEmpty()) { - argumentValues = getArgumentValues(executionContext, fieldArgDefs, field.getArguments()); - } - - - return newExecutionStepInfo() - .type(fieldType) - .fieldDefinition(fieldDefinition) - .fieldContainer(fieldContainer) - .field(field) - .path(parameters.getPath()) - .parentInfo(parentStepInfo) - .arguments(argumentValues) - .build(); - } - - @NonNull - private static Supplier> getArgumentValues(ExecutionContext executionContext, - List fieldArgDefs, - List fieldArgs) { - Supplier> argumentValues; - GraphQLCodeRegistry codeRegistry = executionContext.getGraphQLSchema().getCodeRegistry(); - Supplier> argValuesSupplier = () -> { - Map resolvedValues = ValuesResolver.getArgumentValues(codeRegistry, - fieldArgDefs, - fieldArgs, - executionContext.getCoercedVariables(), - executionContext.getGraphQLContext(), - executionContext.getLocale()); - - return ImmutableMapWithNullValues.copyOf(resolvedValues); - }; - argumentValues = FpKit.intraThreadMemoize(argValuesSupplier); - return argumentValues; + return executionStepInfoFactory.createExecutionStepInfo(executionContext, + parameters, + fieldDefinition, + fieldContainer); } // Errors that result from the execution of deferred fields are kept in the deferred context only.