aboutsummaryrefslogtreecommitdiff
path: root/tests/bench/bench.php
blob: b7ae94ea3d4e1c1e77e411ca5307e58359149408 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
<?php

declare(strict_types=1);

/**
 * Miniroute benchmark — registration and dispatch scaling.
 *
 * Ported from the tmp-miniroute stress test (section 5) so the performance
 * characteristics stay measurable as the kernel evolves. Run manually:
 *
 *     composer bench          # or: php tests/bench/bench.php
 *
 * Timings are intentionally NOT PHPUnit assertions — wall-clock numbers are
 * too flaky for CI. The v1.0.0 release (miniroute-18h.3) adds hard CI gates
 * (register < 5ms for the full table, dispatch < 0.1ms).
 *
 * Expected shape: registration is super-linear in v0.1.0 (usort per add,
 * ~770ms at 600 routes) and collapses once miniroute-uhs.5 (lazy sort)
 * lands. Dispatch scales linearly in route count.
 */

require dirname(__DIR__) . '/bootstrap.php';

use BrettParson\MiniRoute\Attribute\Get;
use BrettParson\MiniRoute\Http\RequestInterface;
use BrettParson\MiniRoute\Http\ResponseInterface;
use BrettParson\MiniRoute\Routing\RouteNotFoundException;
use BrettParson\MiniRoute\Routing\Router;
use BrettParson\MiniRoute\Tests\Fixtures\AdminLoggingMiddleware;
use BrettParson\MiniRoute\Tests\Fixtures\CsrfLoggingMiddleware;
use BrettParson\MiniRoute\Tests\Fixtures\FakeResolver;
use BrettParson\MiniRoute\Tests\Fixtures\RealisticAdminController;
use BrettParson\MiniRoute\Tests\Fixtures\RealisticPublicController;
use BrettParson\MiniRoute\Tests\Fixtures\TestRequest;
use BrettParson\MiniRoute\Tests\Fixtures\TestResponse;

/** @return ResponseInterface|null body, or null on no-match */
function benchHit(Router $router, string $method, string $path): ?string
{
    try {
        return $router->dispatch(new TestRequest($method, $path))->body();
    } catch (RouteNotFoundException) {
        return null;
    }
}

function benchMs(float $start): float
{
    return (microtime(true) - $start) * 1000;
}

echo "miniroute benchmark\n";
echo str_repeat('-', 64) . "\n";

// ── Realistic table: registration + 2000 dispatches ────────────────────────

$resolver = new FakeResolver();
$resolver->bind(RealisticPublicController::class, new RealisticPublicController());
$resolver->bind(RealisticAdminController::class, new RealisticAdminController());
$resolver->bind(AdminLoggingMiddleware::class, new AdminLoggingMiddleware(new ArrayObject()));
$resolver->bind(CsrfLoggingMiddleware::class, new CsrfLoggingMiddleware(new ArrayObject()));

$real = new Router($resolver);
$real->group('/admin', [AdminLoggingMiddleware::class]);
$real->group('/admin', [CsrfLoggingMiddleware::class], ['POST']);

$start = microtime(true);
$real->registerController(RealisticPublicController::class);
$real->registerController(RealisticAdminController::class);
$regMs = benchMs($start);

$paths = ['/entries', '/entries/hello-world', '/notes/42', '/admin', '/admin/notes', '/admin/entries/preview', '/feed.xml', '/robots.txt', '/sitemap.xml'];
$start = microtime(true);
for ($i = 0; $i < 2000; $i++) {
    benchHit($real, 'GET', $paths[$i % count($paths)]);
}
$dispatchMs = benchMs($start);

printf("realistic (22 routes): register = %8.3fms | 2000 dispatches = %8.2fms (%0.5fms each)\n", $regMs, $dispatchMs, $dispatchMs / 2000);

// ── Scaling: synthetic routes at 100 / 300 / 600 ───────────────────────────

printf("\n%-22s %-20s %s\n", 'routes', 'register', 'leaf dispatch (each)');

foreach ([100, 300, 600] as $n) {
    $resolver = new FakeResolver();
    $bench = new Router($resolver);

    $start = microtime(true);
    for ($i = 0; $i < $n; $i++) {
        $class = 'BenchRoute' . $n . '_' . $i;
        eval('final class ' . $class . ' { #[' . Get::class . '("/bench/' . $n . '/' . $i . '")] public function run(): ' . ResponseInterface::class . ' { return new ' . TestResponse::class . '(200, "b"); } }');
        $resolver->bind($class, new $class());
        $bench->registerController($class);
    }
    $regMs = benchMs($start);

    $start = microtime(true);
    for ($i = 0; $i < 200; $i++) {
        benchHit($bench, 'GET', '/bench/' . $n . '/' . ($n - 1));
    }
    $dispatchMs = benchMs($start) / 200;

    printf("%-22d %8.1fms %s %0.5fms\n", $n, $regMs, str_repeat(' ', 11), $dispatchMs);
}

echo str_repeat('-', 64) . "\n";