mirror of
https://github.com/retailcrm/graphql-php.git
synced 2024-11-22 04:46:04 +03:00
Added first meaningful benchmarks to have some grounds for future performance optimizations
This commit is contained in:
parent
34ca931533
commit
66acb73a47
90
benchmarks/HugeSchemaBench.php
Normal file
90
benchmarks/HugeSchemaBench.php
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<?php
|
||||||
|
namespace GraphQL\Benchmarks;
|
||||||
|
|
||||||
|
use GraphQL\GraphQL;
|
||||||
|
use GraphQL\Schema;
|
||||||
|
use GraphQL\Benchmarks\Utils\QueryGenerator;
|
||||||
|
use GraphQL\Benchmarks\Utils\SchemaGenerator;
|
||||||
|
use GraphQL\Type\LazyResolution;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @BeforeMethods({"setUp"})
|
||||||
|
* @OutputTimeUnit("milliseconds", precision=3)
|
||||||
|
* @Warmup(1)
|
||||||
|
* @Revs(5)
|
||||||
|
* @Iterations(1)
|
||||||
|
*/
|
||||||
|
class HugeSchemaBench
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var SchemaGenerator
|
||||||
|
*/
|
||||||
|
private $schemaBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $descriptor;
|
||||||
|
|
||||||
|
private $schema;
|
||||||
|
|
||||||
|
private $lazySchema;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $smallQuery;
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
$this->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,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -29,7 +29,7 @@ class LexerBench
|
|||||||
$lexer = new Lexer($this->introQuery);
|
$lexer = new Lexer($this->introQuery);
|
||||||
|
|
||||||
do {
|
do {
|
||||||
$token = $lexer->nextToken();
|
$token = $lexer->advance();
|
||||||
} while ($token->kind !== Token::EOF);
|
} while ($token->kind !== Token::EOF);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
97
benchmarks/StarWarsBench.php
Normal file
97
benchmarks/StarWarsBench.php
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
<?php
|
||||||
|
namespace GraphQL\Benchmarks;
|
||||||
|
use GraphQL\GraphQL;
|
||||||
|
use GraphQL\Tests\StarWarsSchema;
|
||||||
|
use GraphQL\Type\Introspection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @BeforeMethods({"setIntroQuery"})
|
||||||
|
* @OutputTimeUnit("milliseconds", precision=3)
|
||||||
|
* @Warmup(2)
|
||||||
|
* @Revs(10)
|
||||||
|
* @Iterations(2)
|
||||||
|
*/
|
||||||
|
class StarWarsBench
|
||||||
|
{
|
||||||
|
private $introQuery;
|
||||||
|
|
||||||
|
public function setIntroQuery()
|
||||||
|
{
|
||||||
|
$this->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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
100
benchmarks/Utils/QueryGenerator.php
Normal file
100
benchmarks/Utils/QueryGenerator.php
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
<?php
|
||||||
|
namespace GraphQL\Benchmarks\Utils;
|
||||||
|
|
||||||
|
use GraphQL\Language\AST\DocumentNode;
|
||||||
|
use GraphQL\Language\AST\FieldNode;
|
||||||
|
use GraphQL\Language\AST\NameNode;
|
||||||
|
use GraphQL\Language\AST\OperationDefinitionNode;
|
||||||
|
use GraphQL\Language\AST\SelectionSetNode;
|
||||||
|
use GraphQL\Language\Printer;
|
||||||
|
use GraphQL\Schema;
|
||||||
|
use GraphQL\Type\Definition\FieldDefinition;
|
||||||
|
use GraphQL\Type\Definition\InterfaceType;
|
||||||
|
use GraphQL\Type\Definition\ObjectType;
|
||||||
|
use GraphQL\Type\Definition\WrappingType;
|
||||||
|
use GraphQL\Utils;
|
||||||
|
|
||||||
|
class QueryGenerator
|
||||||
|
{
|
||||||
|
private $schema;
|
||||||
|
|
||||||
|
private $maxLeafFields;
|
||||||
|
|
||||||
|
private $currentLeafFields;
|
||||||
|
|
||||||
|
public function __construct(Schema $schema, $percentOfLeafFields)
|
||||||
|
{
|
||||||
|
$this->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;
|
||||||
|
}
|
||||||
|
}
|
159
benchmarks/Utils/SchemaGenerator.php
Normal file
159
benchmarks/Utils/SchemaGenerator.php
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
<?php
|
||||||
|
namespace GraphQL\Benchmarks\Utils;
|
||||||
|
|
||||||
|
use GraphQL\Schema;
|
||||||
|
use GraphQL\Type\Definition\EnumType;
|
||||||
|
use GraphQL\Type\Definition\InputObjectType;
|
||||||
|
use GraphQL\Type\Definition\ObjectType;
|
||||||
|
use GraphQL\Type\Definition\Type;
|
||||||
|
|
||||||
|
class SchemaGenerator
|
||||||
|
{
|
||||||
|
private $config = [
|
||||||
|
'totalTypes' => 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';
|
||||||
|
}
|
||||||
|
}
|
@ -13,7 +13,8 @@
|
|||||||
"ext-mbstring": "*"
|
"ext-mbstring": "*"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^4.8"
|
"phpunit/phpunit": "^4.8",
|
||||||
|
"phpbench/phpbench": "^0.13.0"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"bin-dir": "bin"
|
"bin-dir": "bin"
|
||||||
|
Loading…
Reference in New Issue
Block a user