From 66acb73a47ab88261048bb2ceb27db5807c5fc61 Mon Sep 17 00:00:00 2001 From: Vladimir Razuvaev Date: Sat, 25 Feb 2017 02:27:53 +0700 Subject: [PATCH] Added first meaningful benchmarks to have some grounds for future performance optimizations --- benchmarks/HugeSchemaBench.php | 90 +++++++++++++++ benchmarks/LexerBench.php | 2 +- benchmarks/StarWarsBench.php | 97 ++++++++++++++++ benchmarks/Utils/QueryGenerator.php | 100 +++++++++++++++++ benchmarks/Utils/SchemaGenerator.php | 159 +++++++++++++++++++++++++++ composer.json | 3 +- 6 files changed, 449 insertions(+), 2 deletions(-) create mode 100644 benchmarks/HugeSchemaBench.php create mode 100644 benchmarks/StarWarsBench.php create mode 100644 benchmarks/Utils/QueryGenerator.php create mode 100644 benchmarks/Utils/SchemaGenerator.php diff --git a/benchmarks/HugeSchemaBench.php b/benchmarks/HugeSchemaBench.php new file mode 100644 index 0000000..d641bee --- /dev/null +++ b/benchmarks/HugeSchemaBench.php @@ -0,0 +1,90 @@ +schemaBuilder = new SchemaGenerator([ + 'totalTypes' => 600, + 'fieldsPerType' => 8, + 'listFieldsPerType' => 2, + 'nestingLevel' => 10 + ]); + + $this->schema = $this->schemaBuilder->buildSchema(); + + $queryBuilder = new QueryGenerator($this->schema, 0.05); + $this->descriptor = $this->schema->getDescriptor(); + $this->smallQuery = $queryBuilder->buildQuery(); + } + + public function benchSchema() + { + $this->schemaBuilder + ->buildSchema(); + } + + public function benchSchemaLazy() + { + $this->createLazySchema(); + } + + public function benchSmallQuery() + { + $result = GraphQL::execute($this->schema, $this->smallQuery); + } + + public function benchSmallQueryLazy() + { + $schema = $this->createLazySchema(); + $result = GraphQL::execute($schema, $this->smallQuery); + } + + private function createLazySchema() + { + $strategy = new LazyResolution( + $this->descriptor, + function($name) { + return $this->schemaBuilder->loadType($name); + } + ); + + return new Schema([ + 'query' => $this->schemaBuilder->buildQueryType(), + 'typeResolution' => $strategy, + ]); + } +} diff --git a/benchmarks/LexerBench.php b/benchmarks/LexerBench.php index 5f83cd8..ca79169 100644 --- a/benchmarks/LexerBench.php +++ b/benchmarks/LexerBench.php @@ -29,7 +29,7 @@ class LexerBench $lexer = new Lexer($this->introQuery); do { - $token = $lexer->nextToken(); + $token = $lexer->advance(); } while ($token->kind !== Token::EOF); } } diff --git a/benchmarks/StarWarsBench.php b/benchmarks/StarWarsBench.php new file mode 100644 index 0000000..1690808 --- /dev/null +++ b/benchmarks/StarWarsBench.php @@ -0,0 +1,97 @@ +introQuery = Introspection::getIntrospectionQuery(); + } + + public function benchSchema() + { + StarWarsSchema::build(); + } + + public function benchHeroQuery() + { + $q = ' + query HeroNameQuery { + hero { + name + } + } + '; + + GraphQL::execute( + StarWarsSchema::build(), + $q + ); + } + + public function benchNestedQuery() + { + $q = ' + query NestedQuery { + hero { + name + friends { + name + appearsIn + friends { + name + } + } + } + } + '; + GraphQL::execute( + StarWarsSchema::build(), + $q + ); + } + + public function benchQueryWithFragment() + { + $q = ' + query UseFragment { + luke: human(id: "1000") { + ...HumanFragment + } + leia: human(id: "1003") { + ...HumanFragment + } + } + + fragment HumanFragment on Human { + name + homePlanet + } + '; + + GraphQL::execute( + StarWarsSchema::build(), + $q + ); + } + + public function benchStarWarsIntrospectionQuery() + { + GraphQL::execute( + StarWarsSchema::build(), + $this->introQuery + ); + } +} diff --git a/benchmarks/Utils/QueryGenerator.php b/benchmarks/Utils/QueryGenerator.php new file mode 100644 index 0000000..f89b5be --- /dev/null +++ b/benchmarks/Utils/QueryGenerator.php @@ -0,0 +1,100 @@ +schema = $schema; + + Utils::invariant(0 < $percentOfLeafFields && $percentOfLeafFields <= 1); + + $totalFields = 0; + foreach ($schema->getTypeMap() as $type) { + if ($type instanceof ObjectType) { + $totalFields += count($type->getFields()); + } + } + + $this->maxLeafFields = max(1, round($totalFields * $percentOfLeafFields)); + $this->currentLeafFields = 0; + } + + public function buildQuery() + { + $qtype = $this->schema->getQueryType(); + + $ast = new DocumentNode([ + 'definitions' => [ + new OperationDefinitionNode([ + 'name' => new NameNode(['value' => 'TestQuery']), + 'operation' => 'query', + 'selectionSet' => $this->buildSelectionSet($qtype->getFields()) + ]) + ] + ]); + + return Printer::doPrint($ast); + } + + /** + * @param FieldDefinition[] $fields + * @return SelectionSetNode + */ + public function buildSelectionSet($fields) + { + $selections[] = new FieldNode([ + 'name' => new NameNode(['value' => '__typename']) + ]); + $this->currentLeafFields++; + + foreach ($fields as $field) { + if ($this->currentLeafFields >= $this->maxLeafFields) { + break; + } + + $type = $field->getType(); + + if ($type instanceof WrappingType) { + $type = $type->getWrappedType(true); + } + + if ($type instanceof ObjectType || $type instanceof InterfaceType) { + $selectionSet = $this->buildSelectionSet($type->getFields()); + } else { + $selectionSet = null; + $this->currentLeafFields++; + } + + $selections[] = new FieldNode([ + 'name' => new NameNode(['value' => $field->name]), + 'selectionSet' => $selectionSet + ]); + } + + $selectionSet = new SelectionSetNode([ + 'selections' => $selections + ]); + + return $selectionSet; + } +} diff --git a/benchmarks/Utils/SchemaGenerator.php b/benchmarks/Utils/SchemaGenerator.php new file mode 100644 index 0000000..6cc9145 --- /dev/null +++ b/benchmarks/Utils/SchemaGenerator.php @@ -0,0 +1,159 @@ + 100, + 'nestingLevel' => 10, + 'fieldsPerType' => 10, + 'listFieldsPerType' => 2 + ]; + + private $typeIndex = 0; + + private $objectTypes = []; + + /** + * BenchmarkSchemaBuilder constructor. + * @param array $config + */ + public function __construct(array $config) + { + $this->config = array_merge($this->config, $config); + } + + public function buildSchema() + { + return new Schema([ + 'query' => $this->buildQueryType() + ]); + } + + public function buildQueryType() + { + $this->typeIndex = 0; + $this->objectTypes = []; + + return $this->createType(0); + } + + public function loadType($name) + { + $tokens = explode('_', $name); + $nestingLevel = (int) $tokens[1]; + + return $this->createType($nestingLevel, $name); + } + + protected function createType($nestingLevel, $typeName = null) + { + if ($this->typeIndex > $this->config['totalTypes']) { + throw new \Exception( + "Cannot create new type: there are already {$this->typeIndex} ". + "which exceeds allowed number of {$this->config['totalTypes']} types total" + ); + } + + $this->typeIndex++; + if (!$typeName) { + $typeName = 'Level_' . $nestingLevel . '_Type' . $this->typeIndex; + } + + $type = new ObjectType([ + 'name' => $typeName, + 'fields' => function() use ($typeName, $nestingLevel) { + return $this->createTypeFields($typeName, $nestingLevel + 1); + } + ]); + + $this->objectTypes[$typeName] = $type; + return $type; + } + + protected function getFieldTypeAndName($nestingLevel, $fieldIndex) + { + if ($nestingLevel >= $this->config['nestingLevel']) { + $fieldType = Type::string(); + $fieldName = 'leafField' . $fieldIndex; + } else if ($this->typeIndex >= $this->config['totalTypes']) { + $fieldType = $this->objectTypes[array_rand($this->objectTypes)]; + $fieldName = 'randomTypeField' . $fieldIndex; + } else { + $fieldType = $this->createType($nestingLevel); + $fieldName = 'field' . $fieldIndex; + } + + return [$fieldType, $fieldName]; + } + + protected function createTypeFields($typeName, $nestingLevel) + { + $fields = []; + for ($index = 0; $index < $this->config['fieldsPerType']; $index++) { + list($type, $name) = $this->getFieldTypeAndName($nestingLevel, $index); + $fields[] = [ + 'name' => $name, + 'type' => $type, + 'resolve' => [$this, 'resolveField'] + ]; + } + for ($index = 0; $index < $this->config['listFieldsPerType']; $index++) { + list($type, $name) = $this->getFieldTypeAndName($nestingLevel, $index); + $name = 'listOf' . ucfirst($name); + + $fields[] = [ + 'name' => $name, + 'type' => Type::listOf($type), + 'args' => $this->createFieldArgs($name, $typeName), + 'resolve' => function() { + return [ + 'string1', + 'string2', + 'string3', + 'string4', + 'string5', + ]; + } + ]; + } + return $fields; + } + + protected function createFieldArgs($fieldName, $typeName) + { + return [ + 'argString' => [ + 'type' => Type::string() + ], + 'argEnum' => [ + 'type' => new EnumType([ + 'name' => $typeName . $fieldName . 'Enum', + 'values' => [ + "ONE", "TWO", "THREE" + ] + ]) + ], + 'argInputObject' => [ + 'type' => new InputObjectType([ + 'name' => $typeName . $fieldName . 'Input', + 'fields' => [ + 'field1' => Type::string(), + 'field2' => Type::int() + ] + ]) + ] + ]; + } + + public function resolveField($value, $args, $context, $resolveInfo) + { + return $resolveInfo->fieldName . '-value'; + } +} diff --git a/composer.json b/composer.json index 9fd5e42..a50e944 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,8 @@ "ext-mbstring": "*" }, "require-dev": { - "phpunit/phpunit": "^4.8" + "phpunit/phpunit": "^4.8", + "phpbench/phpbench": "^0.13.0" }, "config": { "bin-dir": "bin"