Added first meaningful benchmarks to have some grounds for future performance optimizations

This commit is contained in:
Vladimir Razuvaev 2017-02-25 02:27:53 +07:00
parent 34ca931533
commit 66acb73a47
6 changed files with 449 additions and 2 deletions

View 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,
]);
}
}

View File

@ -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);
} }
} }

View 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
);
}
}

View 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;
}
}

View 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';
}
}

View File

@ -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"