Skip to content

Commit b7e9e46

Browse files
author
Raymie Stata
committed
Add FastSchemaGenerator and JMH benchmark infrastructure
Add BuildSchemaBenchmark to measure schema construction performance in isolation from SDL parsing. This benchmark compares standard SchemaGenerator against FastSchemaGenerator using the large-schema-4.graphqls test schema (~18,800 types). Changes: - Add FastSchemaGenerator that uses FastBuilder for optimized schema construction from pre-parsed TypeDefinitionRegistry - Add BuildSchemaBenchmark.java with side-by-side comparison of standard vs fast schema generation, parsing SDL once in @setup to isolate build performance - Add jmhProfilers gradle property support for GC profiling (e.g., -PjmhProfilers="gc") - Add FastSchemaGeneratorTest to verify equivalence with standard SchemaGenerator Usage: ./gradlew jmh -PjmhInclude=".*BuildSchemaBenchmark.*" ./gradlew jmh -PjmhInclude=".*BuildSchemaBenchmark.*" -PjmhProfilers="gc"
1 parent e12c074 commit b7e9e46

File tree

6 files changed

+373
-2
lines changed

6 files changed

+373
-2
lines changed

build.gradle

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,15 @@ tasks.named('jmhJar') {
223223
}
224224
}
225225

226+
jmh {
227+
if (project.hasProperty('jmhInclude')) {
228+
includes = [project.property('jmhInclude')]
229+
}
230+
if (project.hasProperty('jmhProfilers')) {
231+
profilers = [project.property('jmhProfilers')]
232+
}
233+
}
234+
226235

227236
task extractWithoutGuava(type: Copy) {
228237
from({ zipTree({ "build/libs/graphql-java-${project.version}.jar" }) }) {
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package benchmark;
2+
3+
import graphql.schema.GraphQLSchema;
4+
import graphql.schema.idl.FastSchemaGenerator;
5+
import graphql.schema.idl.RuntimeWiring;
6+
import graphql.schema.idl.SchemaGenerator;
7+
import graphql.schema.idl.SchemaParser;
8+
import graphql.schema.idl.TypeDefinitionRegistry;
9+
import org.openjdk.jmh.annotations.Benchmark;
10+
import org.openjdk.jmh.annotations.BenchmarkMode;
11+
import org.openjdk.jmh.annotations.Fork;
12+
import org.openjdk.jmh.annotations.Measurement;
13+
import org.openjdk.jmh.annotations.Mode;
14+
import org.openjdk.jmh.annotations.OutputTimeUnit;
15+
import org.openjdk.jmh.annotations.Scope;
16+
import org.openjdk.jmh.annotations.Setup;
17+
import org.openjdk.jmh.annotations.State;
18+
import org.openjdk.jmh.annotations.Warmup;
19+
import org.openjdk.jmh.infra.Blackhole;
20+
21+
import java.util.concurrent.TimeUnit;
22+
23+
@Warmup(iterations = 2, time = 5)
24+
@Measurement(iterations = 3)
25+
@Fork(2)
26+
@State(Scope.Benchmark)
27+
public class BuildSchemaBenchmark {
28+
29+
static String largeSDL = BenchmarkUtils.loadResource("large-schema-4.graphqls");
30+
31+
private TypeDefinitionRegistry registry;
32+
33+
@Setup
34+
public void setup() {
35+
// Parse SDL once before benchmarks run
36+
registry = new SchemaParser().parse(largeSDL);
37+
}
38+
39+
@Benchmark
40+
@BenchmarkMode(Mode.AverageTime)
41+
@OutputTimeUnit(TimeUnit.MILLISECONDS)
42+
public void benchmarkBuildSchemaAvgTime(Blackhole blackhole) {
43+
blackhole.consume(new SchemaGenerator().makeExecutableSchema(registry, RuntimeWiring.MOCKED_WIRING));
44+
}
45+
46+
@Benchmark
47+
@BenchmarkMode(Mode.AverageTime)
48+
@OutputTimeUnit(TimeUnit.MILLISECONDS)
49+
public void benchmarkBuildSchemaAvgTimeFast(Blackhole blackhole) {
50+
blackhole.consume(new FastSchemaGenerator().makeExecutableSchema(registry, RuntimeWiring.MOCKED_WIRING));
51+
}
52+
}

src/jmh/java/benchmark/CreateSchemaBenchmark.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package benchmark;
22

33
import graphql.schema.GraphQLSchema;
4+
import graphql.schema.idl.FastSchemaGenerator;
45
import graphql.schema.idl.RuntimeWiring;
56
import graphql.schema.idl.SchemaGenerator;
67
import graphql.schema.idl.SchemaParser;
@@ -21,7 +22,7 @@
2122
@Fork(2)
2223
public class CreateSchemaBenchmark {
2324

24-
static String largeSDL = BenchmarkUtils.loadResource("large-schema-3.graphqls");
25+
static String largeSDL = BenchmarkUtils.loadResource("large-schema-4.graphqls");
2526

2627
@Benchmark
2728
@BenchmarkMode(Mode.Throughput)
@@ -37,11 +38,23 @@ public void benchmarkLargeSchemaCreateAvgTime(Blackhole blackhole) {
3738
blackhole.consume(createSchema(largeSDL));
3839
}
3940

41+
@Benchmark
42+
@BenchmarkMode(Mode.AverageTime)
43+
@OutputTimeUnit(TimeUnit.MILLISECONDS)
44+
public void benchmarkLargeSchemaCreateAvgTimeFast(Blackhole blackhole) {
45+
blackhole.consume(createSchemaFast(largeSDL));
46+
}
47+
4048
private static GraphQLSchema createSchema(String sdl) {
4149
TypeDefinitionRegistry registry = new SchemaParser().parse(sdl);
4250
return new SchemaGenerator().makeExecutableSchema(registry, RuntimeWiring.MOCKED_WIRING);
4351
}
4452

53+
private static GraphQLSchema createSchemaFast(String sdl) {
54+
TypeDefinitionRegistry registry = new SchemaParser().parse(sdl);
55+
return new FastSchemaGenerator().makeExecutableSchema(registry, RuntimeWiring.MOCKED_WIRING);
56+
}
57+
4558
@SuppressWarnings("InfiniteLoopStatement")
4659
/// make this a main method if you want to run it in JProfiler etc..
4760
public static void mainXXX(String[] args) {
@@ -54,4 +67,4 @@ public static void mainXXX(String[] args) {
5467
}
5568
}
5669
}
57-
}
70+
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package graphql.schema.idl;
2+
3+
import graphql.Internal;
4+
import graphql.language.OperationTypeDefinition;
5+
import graphql.schema.GraphQLCodeRegistry;
6+
import graphql.schema.GraphQLDirective;
7+
import graphql.schema.GraphQLObjectType;
8+
import graphql.schema.GraphQLSchema;
9+
import graphql.schema.GraphQLType;
10+
11+
import java.util.Map;
12+
import java.util.Set;
13+
14+
import static graphql.schema.idl.SchemaGeneratorHelper.buildDescription;
15+
16+
/**
17+
* A schema generator that uses GraphQLSchema.FastBuilder for improved performance.
18+
* This is intended for benchmarking and performance testing purposes.
19+
*/
20+
@Internal
21+
public class FastSchemaGenerator {
22+
23+
private final SchemaGeneratorHelper schemaGeneratorHelper = new SchemaGeneratorHelper();
24+
25+
/**
26+
* Creates an executable schema from a TypeDefinitionRegistry using FastBuilder.
27+
* This method is optimized for performance and skips validation by default.
28+
*
29+
* @param typeRegistry the type definition registry
30+
* @param wiring the runtime wiring
31+
* @return an executable schema
32+
*/
33+
public GraphQLSchema makeExecutableSchema(TypeDefinitionRegistry typeRegistry, RuntimeWiring wiring) {
34+
return makeExecutableSchema(SchemaGenerator.Options.defaultOptions(), typeRegistry, wiring);
35+
}
36+
37+
/**
38+
* Creates an executable schema from a TypeDefinitionRegistry using FastBuilder.
39+
*
40+
* @param options the schema generation options
41+
* @param typeRegistry the type definition registry
42+
* @param wiring the runtime wiring
43+
* @return an executable schema
44+
*/
45+
public GraphQLSchema makeExecutableSchema(SchemaGenerator.Options options, TypeDefinitionRegistry typeRegistry, RuntimeWiring wiring) {
46+
// Make a copy and add default directives
47+
TypeDefinitionRegistry typeRegistryCopy = new TypeDefinitionRegistry();
48+
typeRegistryCopy.merge(typeRegistry);
49+
schemaGeneratorHelper.addDirectivesIncludedByDefault(typeRegistryCopy);
50+
51+
// Use immutable registry for faster operations
52+
ImmutableTypeDefinitionRegistry fasterImmutableRegistry = typeRegistryCopy.readOnly();
53+
54+
Map<String, OperationTypeDefinition> operationTypeDefinitions = SchemaExtensionsChecker.gatherOperationDefs(fasterImmutableRegistry);
55+
56+
return makeExecutableSchemaImpl(fasterImmutableRegistry, wiring, operationTypeDefinitions, options);
57+
}
58+
59+
private GraphQLSchema makeExecutableSchemaImpl(ImmutableTypeDefinitionRegistry typeRegistry,
60+
RuntimeWiring wiring,
61+
Map<String, OperationTypeDefinition> operationTypeDefinitions,
62+
SchemaGenerator.Options options) {
63+
// Build all types using the standard helper
64+
SchemaGeneratorHelper.BuildContext buildCtx = new SchemaGeneratorHelper.BuildContext(
65+
typeRegistry, wiring, operationTypeDefinitions, options);
66+
67+
// Build directives
68+
Set<GraphQLDirective> additionalDirectives = schemaGeneratorHelper.buildAdditionalDirectiveDefinitions(buildCtx);
69+
70+
// Use a dummy builder to trigger type building (this populates buildCtx)
71+
GraphQLSchema.Builder tempBuilder = GraphQLSchema.newSchema();
72+
schemaGeneratorHelper.buildOperations(buildCtx, tempBuilder);
73+
74+
// Build all additional types
75+
Set<GraphQLType> additionalTypes = schemaGeneratorHelper.buildAdditionalTypes(buildCtx);
76+
77+
// Set field visibility on code registry
78+
buildCtx.getCodeRegistry().fieldVisibility(buildCtx.getWiring().getFieldVisibility());
79+
80+
// Build the code registry
81+
GraphQLCodeRegistry codeRegistry = buildCtx.getCodeRegistry().build();
82+
83+
// Extract operation types by name from built types
84+
Set<GraphQLType> allBuiltTypes = buildCtx.getTypes();
85+
86+
// Get the actual type names from operationTypeDefinitions, defaulting to standard names
87+
String queryTypeName = getOperationTypeName(operationTypeDefinitions, "query", "Query");
88+
String mutationTypeName = getOperationTypeName(operationTypeDefinitions, "mutation", "Mutation");
89+
String subscriptionTypeName = getOperationTypeName(operationTypeDefinitions, "subscription", "Subscription");
90+
91+
GraphQLObjectType queryType = findOperationType(allBuiltTypes, queryTypeName);
92+
GraphQLObjectType mutationType = findOperationType(allBuiltTypes, mutationTypeName);
93+
GraphQLObjectType subscriptionType = findOperationType(allBuiltTypes, subscriptionTypeName);
94+
95+
if (queryType == null) {
96+
throw new IllegalStateException("Query type '" + queryTypeName + "' is required but was not found");
97+
}
98+
99+
// Create FastBuilder
100+
GraphQLSchema.FastBuilder fastBuilder = new GraphQLSchema.FastBuilder(
101+
GraphQLCodeRegistry.newCodeRegistry(codeRegistry),
102+
queryType,
103+
mutationType,
104+
subscriptionType);
105+
106+
// Add all built types
107+
fastBuilder.additionalTypes(allBuiltTypes);
108+
fastBuilder.additionalTypes(additionalTypes);
109+
110+
// Add all directive definitions
111+
fastBuilder.additionalDirectives(additionalDirectives);
112+
113+
// Add schema description if present
114+
typeRegistry.schemaDefinition().ifPresent(schemaDefinition -> {
115+
String description = buildDescription(buildCtx, schemaDefinition, schemaDefinition.getDescription());
116+
fastBuilder.description(description);
117+
});
118+
119+
// Add schema definition
120+
fastBuilder.definition(typeRegistry.schemaDefinition().orElse(null));
121+
122+
// Disable validation for performance
123+
fastBuilder.withValidation(false);
124+
125+
return fastBuilder.build();
126+
}
127+
128+
private String getOperationTypeName(Map<String, OperationTypeDefinition> operationTypeDefs,
129+
String operationName,
130+
String defaultTypeName) {
131+
OperationTypeDefinition opDef = operationTypeDefs.get(operationName);
132+
if (opDef != null) {
133+
return opDef.getTypeName().getName();
134+
}
135+
return defaultTypeName;
136+
}
137+
138+
private GraphQLObjectType findOperationType(Set<GraphQLType> types, String typeName) {
139+
for (GraphQLType type : types) {
140+
if (type instanceof GraphQLObjectType) {
141+
GraphQLObjectType objectType = (GraphQLObjectType) type;
142+
if (objectType.getName().equals(typeName)) {
143+
return objectType;
144+
}
145+
}
146+
}
147+
return null;
148+
}
149+
}

src/main/java/graphql/schema/idl/SchemaGeneratorHelper.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,17 @@ public Set<GraphQLDirective> getDirectives() {
210210
return directives;
211211
}
212212

213+
/**
214+
* Returns all types that have been built so far (both input and output types).
215+
* This is used by FastSchemaGenerator to collect types for FastBuilder.
216+
*/
217+
public Set<GraphQLType> getTypes() {
218+
Set<GraphQLType> allTypes = new LinkedHashSet<>();
219+
allTypes.addAll(outputGTypes.values());
220+
allTypes.addAll(inputGTypes.values());
221+
return allTypes;
222+
}
223+
213224
public boolean isCaptureAstDefinitions() {
214225
return options.isCaptureAstDefinitions();
215226
}

0 commit comments

Comments
 (0)