Skip to content

Commit

Permalink
Add PHPBench to the project
Browse files Browse the repository at this point in the history
Allowing us to track performance changes more easily.

Inspiration for benchmark scenarios:

 - https://github.com/TimeToogo/RouterBenchmark
 - https://medium.com/@nicolas.grekas/making-symfonys-router-77-7x-faster-1-2-958e3754f0e1
  • Loading branch information
lcobucci committed Jun 5, 2019
1 parent 8bd8c79 commit f571a12
Show file tree
Hide file tree
Showing 6 changed files with 312 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .gitattributes
@@ -1,6 +1,8 @@
/benchmark export-ignore
/test export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/.travis.yml export-ignore
/*.dist export-ignore
/psalm.xml export-ignore
/phpbench.json export-ignore
98 changes: 98 additions & 0 deletions benchmark/Dispatching.php
@@ -0,0 +1,98 @@
<?php
declare(strict_types=1);

namespace FastRoute\Benchmark;

use FastRoute\DataGenerator;
use FastRoute\Dispatcher;
use PhpBench\Benchmark\Metadata\Annotations\BeforeMethods;
use PhpBench\Benchmark\Metadata\Annotations\Iterations;
use PhpBench\Benchmark\Metadata\Annotations\ParamProviders;
use PhpBench\Benchmark\Metadata\Annotations\Revs;
use PhpBench\Benchmark\Metadata\Annotations\Warmup;
use PHPUnit\Framework\Assert;

/**
* @Warmup(2)
* @Revs(1000)
* @Iterations(5)
* @BeforeMethods({"initializeDispatchers"})
*/
abstract class Dispatching
{
/**
* @var Dispatcher[]
*/
private $dispatchers = [];

public function initializeDispatchers(): void
{
$this->dispatchers['default'] = $this->createDispatcher();
$this->dispatchers['char_count'] = $this->createDispatcher(
[
'dataGenerator' => DataGenerator\CharCountBased::class,
'dispatcher' => Dispatcher\CharCountBased::class,
]
);
$this->dispatchers['group_pos'] = $this->createDispatcher(
[
'dataGenerator' => DataGenerator\GroupPosBased::class,
'dispatcher' => Dispatcher\GroupPosBased::class,
]
);
$this->dispatchers['mark'] = $this->createDispatcher(
[
'dataGenerator' => DataGenerator\MarkBased::class,
'dispatcher' => Dispatcher\MarkBased::class,
]
);
}

abstract protected function createDispatcher(array $options = []): Dispatcher;

abstract public function provideStaticRoutes(): iterable;

abstract public function provideDynamicRoutes(): iterable;

abstract public function provideOtherScenarios(): iterable;

public function provideDispatcher(): iterable
{
yield 'default' => ['dispatcher' => 'default'];
yield 'char-count' => ['dispatcher' => 'char_count'];
yield 'group-pos' => ['dispatcher' => 'group_pos'];
yield 'mark' => ['dispatcher' => 'mark'];
}

/**
* @ParamProviders({"provideDispatcher", "provideStaticRoutes"})
*/
public function benchStaticRoutes(array $params): void
{
$this->runScenario($params);
}

/**
* @ParamProviders({"provideDispatcher", "provideDynamicRoutes"})
*/
public function benchDynamicRoutes(array $params): void
{
$this->runScenario($params);
}

/**
* @ParamProviders({"provideDispatcher", "provideOtherScenarios"})
*/
public function benchOtherRoutes(array $params): void
{
$this->runScenario($params);
}

private function runScenario(array $params): void
{
$dispatcher = $this->dispatchers[$params['dispatcher']];
$result = $dispatcher->dispatch($params['method'], $params['route']);

Assert::assertSame($params['result'], $result);
}
}
75 changes: 75 additions & 0 deletions benchmark/ManyRoutes.php
@@ -0,0 +1,75 @@
<?php
declare(strict_types=1);

namespace FastRoute\Benchmark;

use FastRoute\Dispatcher;
use FastRoute\RouteCollector;
use function FastRoute\simpleDispatcher;

final class ManyRoutes extends Dispatching
{
protected function createDispatcher(array $options = []): Dispatcher
{
return simpleDispatcher(
static function (RouteCollector $routes): void {
for ($i = 0; $i < 400; ++$i) {
$routes->addRoute('GET', '/abc' . $i, ['name' => 'static-' . $i]);
$routes->addRoute('GET', '/abc{foo}/' . $i, ['name' => 'not-static-' . $i]);
}
},
$options
);
}

public function provideStaticRoutes(): iterable
{
yield 'first' => [
'method' => 'GET',
'route' => '/abc0',
'result' => [Dispatcher::FOUND, ['name' => 'static-0'], []],
];

yield 'last' => [
'method' => 'GET',
'route' => '/abc399',
'result' => [Dispatcher::FOUND, ['name' => 'static-399'], []],
];

yield 'invalid-method' => [
'method' => 'PUT',
'route' => '/abc399',
'result' => [Dispatcher::METHOD_NOT_ALLOWED, ['GET']],
];
}

public function provideDynamicRoutes(): iterable
{
yield 'first' => [
'method' => 'GET',
'route' => '/abcbar/0',
'result' => [Dispatcher::FOUND, ['name' => 'not-static-0'], ['foo' => 'bar']],
];

yield 'last' => [
'method' => 'GET',
'route' => '/abcbar/399',
'result' => [Dispatcher::FOUND, ['name' => 'not-static-399'], ['foo' => 'bar']],
];

yield 'invalid-method' => [
'method' => 'PUT',
'route' => '/abcbar/399',
'result' => [Dispatcher::METHOD_NOT_ALLOWED, ['GET']],
];
}

public function provideOtherScenarios(): iterable
{
yield 'non-existent' => [
'method' => 'GET',
'route' => '/testing',
'result' => [Dispatcher::NOT_FOUND],
];
}
}
126 changes: 126 additions & 0 deletions benchmark/RealLifeExample.php
@@ -0,0 +1,126 @@
<?php
declare(strict_types=1);

namespace FastRoute\Benchmark;

use FastRoute\Dispatcher;
use FastRoute\RouteCollector;
use PhpBench\Benchmark\Metadata\Annotations\BeforeMethods;
use PhpBench\Benchmark\Metadata\Annotations\Iterations;
use PhpBench\Benchmark\Metadata\Annotations\Revs;
use function assert;
use function FastRoute\simpleDispatcher;

final class RealLifeExample extends Dispatching
{
protected function createDispatcher(array $options = []): Dispatcher
{
return simpleDispatcher(
static function (RouteCollector $routes): void {
$routes->addRoute('GET', '/', ['name' => 'home']);
$routes->addRoute('GET', '/page/{page_slug:[a-zA-Z0-9\-]+}', ['name' => 'page.show']);
$routes->addRoute('GET', '/about-us', ['name' => 'about-us']);
$routes->addRoute('GET', '/contact-us', ['name' => 'contact-us']);
$routes->addRoute('POST', '/contact-us', ['name' => 'contact-us.submit']);
$routes->addRoute('GET', '/blog', ['name' => 'blog.index']);
$routes->addRoute('GET', '/blog/recent', ['name' => 'blog.recent']);
$routes->addRoute('GET', '/blog/post/{post_slug:[a-zA-Z0-9\-]+}', ['name' => 'blog.post.show']);
$routes->addRoute('POST', '/blog/post/{post_slug:[a-zA-Z0-9\-]+}/comment', ['name' => 'blog.post.comment']);
$routes->addRoute('GET', '/shop', ['name' => 'shop.index']);
$routes->addRoute('GET', '/shop/category', ['name' => 'shop.category.index']);
$routes->addRoute('GET', '/shop/category/search/{filter_by:[a-zA-Z]+}:{filter_value}', ['name' => 'shop.category.search']);
$routes->addRoute('GET', '/shop/category/{category_id:\d+}', ['name' => 'shop.category.show']);
$routes->addRoute('GET', '/shop/category/{category_id:\d+}/product', ['name' => 'shop.category.product.index']);
$routes->addRoute('GET', '/shop/category/{category_id:\d+}/product/search/{filter_by:[a-zA-Z]+}:{filter_value}', ['name' => 'shop.category.product.search']);
$routes->addRoute('GET', '/shop/product', ['name' => 'shop.product.index']);
$routes->addRoute('GET', '/shop/product/search/{filter_by:[a-zA-Z]+}:{filter_value}', ['name' => 'shop.product.search']);
$routes->addRoute('GET', '/shop/product/{product_id:\d+}', ['name' => 'shop.product.show']);
$routes->addRoute('GET', '/shop/cart', ['name' => 'shop.cart.show']);
$routes->addRoute('PUT', '/shop/cart', ['name' => 'shop.cart.add']);
$routes->addRoute('DELETE', '/shop/cart', ['name' => 'shop.cart.empty']);
$routes->addRoute('GET', '/shop/cart/checkout', ['name' => 'shop.cart.checkout.show']);
$routes->addRoute('POST', '/shop/cart/checkout', ['name' => 'shop.cart.checkout.process']);
$routes->addRoute('GET', '/admin/login', ['name' => 'admin.login']);
$routes->addRoute('POST', '/admin/login', ['name' => 'admin.login.submit']);
$routes->addRoute('GET', '/admin/logout', ['name' => 'admin.logout']);
$routes->addRoute('GET', '/admin', ['name' => 'admin.index']);
$routes->addRoute('GET', '/admin/product', ['name' => 'admin.product.index']);
$routes->addRoute('GET', '/admin/product/create', ['name' => 'admin.product.create']);
$routes->addRoute('POST', '/admin/product', ['name' => 'admin.product.store']);
$routes->addRoute('GET', '/admin/product/{product_id:\d+}', ['name' => 'admin.product.show']);
$routes->addRoute('GET', '/admin/product/{product_id:\d+}/edit', ['name' => 'admin.product.edit']);
$routes->addRoute(['PUT', 'PATCH'], '/admin/product/{product_id:\d+}', ['name' => 'admin.product.update']);
$routes->addRoute('DELETE', '/admin/product/{product_id:\d+}', ['name' => 'admin.product.destroy']);
$routes->addRoute('GET', '/admin/category', ['name' => 'admin.category.index']);
$routes->addRoute('GET', '/admin/category/create', ['name' => 'admin.category.create']);
$routes->addRoute('POST', '/admin/category', ['name' => 'admin.category.store']);
$routes->addRoute('GET', '/admin/category/{category_id:\d+}', ['name' => 'admin.category.show']);
$routes->addRoute('GET', '/admin/category/{category_id:\d+}/edit', ['name' => 'admin.category.edit']);
$routes->addRoute(['PUT', 'PATCH'], '/admin/category/{category_id:\d+}', ['name' => 'admin.category.update']);
$routes->addRoute('DELETE', '/admin/category/{category_id:\d+}', ['name' => 'admin.category.destroy']);
},
$options
);
}

public function provideStaticRoutes(): iterable
{
yield 'first' => [
'method' => 'GET',
'route' => '/',
'result' => [Dispatcher::FOUND, ['name' => 'home'], []],
];

yield 'last' => [
'method' => 'GET',
'route' => '/admin/category',
'result' => [Dispatcher::FOUND, ['name' => 'admin.category.index'], []],
];

yield 'invalid-method' => [
'method' => 'PUT',
'route' => '/about-us',
'result' => [Dispatcher::METHOD_NOT_ALLOWED, ['GET']],
];
}

public function provideDynamicRoutes(): iterable
{
yield 'first' => [
'method' => 'GET',
'route' => '/page/hello-word',
'result' => [Dispatcher::FOUND, ['name' => 'page.show'], ['page_slug' => 'hello-word']],
];

yield 'last' => [
'method' => 'GET',
'route' => '/admin/category/123',
'result' => [Dispatcher::FOUND, ['name' => 'admin.category.show'], ['category_id' => '123']],
];

yield 'invalid-method' => [
'method' => 'PATCH',
'route' => '/shop/category/123',
'result' => [Dispatcher::METHOD_NOT_ALLOWED, ['GET']],
];
}

public function provideOtherScenarios(): iterable
{
yield 'non-existent' => [
'method' => 'GET',
'route' => '/shop/product/awesome',
'result' => [Dispatcher::NOT_FOUND],
];

yield 'longest-route' => [
'method' => 'GET',
'route' => '/shop/category/123/product/search/status:sale',
'result' => [
Dispatcher::FOUND,
['name' => 'shop.category.product.search'],
['category_id' => '123', 'filter_by' => 'status', 'filter_value' => 'sale'],
],
];
}
}
4 changes: 3 additions & 1 deletion composer.json
Expand Up @@ -17,14 +17,16 @@
},
"autoload-dev": {
"psr-4": {
"FastRoute\\Benchmark\\": "benchmark/",
"FastRoute\\Test\\": "test/"
}
},
"require": {
"php": ">=7.1.0"
},
"require-dev": {
"phpunit/phpunit": "^7.5"
"phpunit/phpunit": "^7.5",
"phpbench/phpbench": "^0.16.9"
},
"extra": {
"branch-alias": {
Expand Down
8 changes: 8 additions & 0 deletions phpbench.json
@@ -0,0 +1,8 @@
{
"bootstrap": "vendor/autoload.php",
"path": "benchmark",

"extensions": [
"PhpBench\\Extensions\\XDebug\\XDebugExtension"
]
}

0 comments on commit f571a12

Please sign in to comment.