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

Optimize resolvers execution paths #488

Merged
merged 11 commits into from
Jan 3, 2020
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- update `TypeResolver` interface to match with `GraphQLTypeResolver` from `graphql-js`
- add basic support for directives with `@Directive()` decorator (#369)
- add possibility to tune up the performance and disable auth & middlewares stack for simple field resolvers (#479)
- optimize resolvers execution paths to speed up a lot basic scenarios (#488)
### Fixes
- refactor union types function syntax handling to prevent possible errors with circular refs
- fix transforming and validating nested inputs and arrays (#462)
Expand Down
65 changes: 65 additions & 0 deletions benchmarks/array/graphql-js/async.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
GraphQLNonNull,
GraphQLBoolean,
GraphQLInt,
GraphQLList,
} from "graphql";

import { runBenchmark, ARRAY_ITEMS } from "../run";

const SampleObjectType: GraphQLObjectType = new GraphQLObjectType({
name: "SampleObject",
fields: () => ({
stringField: {
type: new GraphQLNonNull(GraphQLString),
resolve: async source => {
return source.stringField;
},
},
numberField: {
type: new GraphQLNonNull(GraphQLInt),
resolve: async source => {
return source.numberField;
},
},
booleanField: {
type: new GraphQLNonNull(GraphQLBoolean),
resolve: async source => {
return source.booleanField;
},
},
nestedField: {
type: SampleObjectType,
resolve: async source => {
return source.nestedField;
},
},
}),
});

const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: "Query",
fields: {
multipleNestedObjects: {
type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(SampleObjectType))),
resolve: () =>
Array.from({ length: ARRAY_ITEMS }, (_, index) => ({
stringField: "stringField",
booleanField: true,
numberField: index,
nestedField: {
stringField: "stringField",
booleanField: true,
numberField: index,
},
})),
},
},
}),
});

runBenchmark(schema).catch(console.error);
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import {
GraphQLList,
} from "graphql";

import { runBenchmark, ARRAY_ITEMS } from "./run";
import { runBenchmark, ARRAY_ITEMS } from "../run";

const SampleObjectType: GraphQLObjectType = new GraphQLObjectType({
name: "SampleObject",
fields: () => ({
sampleField: {
stringField: {
type: new GraphQLNonNull(GraphQLString),
},
numberField: {
Expand Down
31 changes: 25 additions & 6 deletions benchmarks/array/results.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,34 @@
Core i7 2700K @ 3.5GHz
Windows 10 x64
10 000 array items | 100 iterations
25 000 array items | 50 iterations
Node.js v13.5

-----
Node.js v13.2
TypeGraphQL

standard
- 42.551s
- 15.518s

using sync field resolvers
- 18.180s

using async field resolvers
- 39.934s

using getters
- 31.207s

standard with global middleware
- 62.664s

with `simpleResolvers: true`
- 11.841s
- 14.980s

-----
`graphql-js`

standard
- 13.276s

graphql-js
- 9.963s
async field resolvers
- 25.630s
4 changes: 2 additions & 2 deletions benchmarks/array/run.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { GraphQLSchema, execute } from "graphql";
import { gql } from "apollo-server";

const BENCHMARK_ITERATIONS = 100;
export const ARRAY_ITEMS = 10000;
const BENCHMARK_ITERATIONS = 50;
export const ARRAY_ITEMS = 25000;

export async function runBenchmark(schema: GraphQLSchema) {
const multipleNestedObjectsQuery = gql`
Expand Down
78 changes: 78 additions & 0 deletions benchmarks/array/type-graphql/async-field-resolvers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import "reflect-metadata";
import {
buildSchema,
Field,
ObjectType,
Resolver,
Query,
Int,
FieldResolver,
Root,
} from "../../../build/package/dist";

import { runBenchmark, ARRAY_ITEMS } from "../run";

@ObjectType()
class SampleObject {
@Field()
stringField!: string;

@Field(type => Int)
numberField!: number;

@Field()
booleanField!: boolean;

@Field({ nullable: true })
nestedField?: SampleObject;
}

@Resolver(SampleObject)
class SampleResolver {
@Query(returns => [SampleObject])
multipleNestedObjects(): SampleObject[] {
return Array.from(
{ length: ARRAY_ITEMS },
(_, index): SampleObject => ({
stringField: "stringField",
booleanField: true,
numberField: index,
nestedField: {
stringField: "stringField",
booleanField: true,
numberField: index,
},
}),
);
}

@FieldResolver()
async stringField(@Root() source: SampleObject) {
return source.stringField;
}

@FieldResolver()
async numberField(@Root() source: SampleObject) {
return source.numberField;
}

@FieldResolver()
async booleanField(@Root() source: SampleObject) {
return source.booleanField;
}

@FieldResolver()
async nestedField(@Root() source: SampleObject) {
return source.nestedField;
}
}

async function main() {
const schema = await buildSchema({
resolvers: [SampleResolver],
});

await runBenchmark(schema);
}

main().catch(console.error);
65 changes: 65 additions & 0 deletions benchmarks/array/type-graphql/simple-resolvers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import "reflect-metadata";
import {
buildSchema,
Field,
ObjectType,
Resolver,
Query,
Int,
MiddlewareFn,
} from "../../../build/package/dist";

import { runBenchmark, ARRAY_ITEMS } from "../run";

@ObjectType({ simpleResolvers: true })
class SampleObject {
@Field()
stringField!: string;

@Field(type => Int)
numberField!: number;

@Field()
booleanField!: boolean;

@Field({ nullable: true })
nestedField?: SampleObject;
}

@Resolver()
class SampleResolver {
@Query(returns => [SampleObject])
multipleNestedObjects(): SampleObject[] {
return Array.from(
{ length: ARRAY_ITEMS },
(_, index): SampleObject => ({
stringField: "stringField",
booleanField: true,
numberField: index,
nestedField: {
stringField: "stringField",
booleanField: true,
numberField: index,
},
}),
);
}
}

const log = (...args: any[]) => void 0; // noop

const loggingMiddleware: MiddlewareFn = ({ info }, next) => {
log(`${info.parentType.name}.${info.fieldName} accessed`);
return next();
};

async function main() {
const schema = await buildSchema({
resolvers: [SampleResolver],
globalMiddlewares: [loggingMiddleware],
});

await runBenchmark(schema);
}

main().catch(console.error);
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import "reflect-metadata";
import { buildSchema, Field, ObjectType, Resolver, Query, Int } from "../../build/package";
import { buildSchema, Field, ObjectType, Resolver, Query, Int } from "../../../build/package/dist";

import { runBenchmark, ARRAY_ITEMS } from "./run";
import { runBenchmark, ARRAY_ITEMS } from "../run";

@ObjectType({ simpleResolvers: true })
@ObjectType()
class SampleObject {
@Field()
stringField!: string;
Expand Down
78 changes: 78 additions & 0 deletions benchmarks/array/type-graphql/sync-field-resolvers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import "reflect-metadata";
import {
buildSchema,
Field,
ObjectType,
Resolver,
Query,
Int,
FieldResolver,
Root,
} from "../../../build/package/dist";

import { runBenchmark, ARRAY_ITEMS } from "../run";

@ObjectType()
class SampleObject {
@Field()
stringField!: string;

@Field(type => Int)
numberField!: number;

@Field()
booleanField!: boolean;

@Field({ nullable: true })
nestedField?: SampleObject;
}

@Resolver(SampleObject)
class SampleResolver {
@Query(returns => [SampleObject])
multipleNestedObjects(): SampleObject[] {
return Array.from(
{ length: ARRAY_ITEMS },
(_, index): SampleObject => ({
stringField: "stringField",
booleanField: true,
numberField: index,
nestedField: {
stringField: "stringField",
booleanField: true,
numberField: index,
},
}),
);
}

@FieldResolver()
stringField(@Root() source: SampleObject) {
return source.stringField;
}

@FieldResolver()
numberField(@Root() source: SampleObject) {
return source.numberField;
}

@FieldResolver()
booleanField(@Root() source: SampleObject) {
return source.booleanField;
}

@FieldResolver()
nestedField(@Root() source: SampleObject) {
return source.nestedField;
}
}

async function main() {
const schema = await buildSchema({
resolvers: [SampleResolver],
});

await runBenchmark(schema);
}

main().catch(console.error);
Loading