From 5b30dea8fdf4be13450645d90fb1b57c7c24f350 Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Fri, 13 Sep 2024 12:35:42 -0500 Subject: [PATCH] Update readme, exclude tests from package --- README.md | 83 +++++++++++++++----------------------------------- composer.json | 7 ++++- tests/test.php | 22 +++++++------ 3 files changed, 43 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index a68fb8a..04a123c 100644 --- a/README.md +++ b/README.md @@ -33,74 +33,39 @@ One flaw(-ish) of the SimpleRouter implementation (and many other implementation In order to circumvent this, we can rely on our node structure; if a node begins with our delimiter `:` then we can take the related segment from the URI and use that as a parameter, regardless of the value. This means we have extremely low overhead in the logic required to pull parameters from URIs. +To prevent creating many unnecessary children nodes and handlers, all parameters are converted to a `:x` node internally. Any value will satisfy a parameter node. These parameters are then passed into the hanlder function call. ## Performance Of course, what good is a router that's slow? We need to be able to lookup routes and get the handler as quickly as possible. Now, you may note there are multiple routers here; these are implementations in their experimental phase to find the most memory and time efficient lookup operations possible. -For our benchmarks, which you can find in their respective files in [tests](tests/), we create a single instance of a router, load routes from the `.txt` files, write their respective arrays to `.txt` files in [storage](tests/storage/), then perform three iterations each; 10k, 100k, 1m requests. In these iterations, we pick a random URI from the full list, and have the router perform the lookup on that randomly selected URI. The test fails only if a `404` or `405` is returned. - -Below are the results from our most rigorous tests; performing 1 million lookups on 1000 randomized routes with various lengths and parameters. - -### SimpleRouter - -This is an old project of mine and the first router I ever tried to write. Foundationally it relies on tokenizing an incoming URI and matching it to regex, then looking through the internal routes array. +There are three tests performed, each running in iterations of 10000, 100000, 1000000; +- blog: 4 routes +- github: 203 routes +- big: 1000 routes +And one final test to ensure parameter handling is correct. You can run these tests by simply running `php test.php` in the `/tests` directory. +These are my results for the big 1,000,000 test: ```php -Running 1000000 iterations -(100000 lookups) M: 1846.2 kb - T: 32.6156370640 s -(200000 lookups) M: 1846.2 kb - T: 63.9784071445 s -(300000 lookups) M: 1846.2 kb - T: 96.9934570789 s -(400000 lookups) M: 1846.2 kb - T: 130.2443051338 s -(500000 lookups) M: 1846.2 kb - T: 161.8348190784 s -(600000 lookups) M: 1846.3 kb - T: 197.4232161045 s -(700000 lookups) M: 1846.1 kb - T: 231.8421580791 s -(800000 lookups) M: 1846 kb - T: 262.8337080479 s -(900000 lookups) M: 1846.2 kb - T: 296.1434569359 s -Time: 330.9394941330 s -Avg/lookup: 0.0003309396 s +// CPU: 11th gen Intel Core i7-1185G7 @ 3.0GHz +// RAM: 32GB 3200MHz DDR4 +🚀 Running 1000000 iterations... +✔️ Done! +Peak memory: 2726.5 kb +Time: 1.5776410103 s +Avg/lookup: 0.0000008231 s +Shortest lookup: 0.0000009537 s +Longest lookup: 0.0018200874 s ``` -Interestingly, it has the lowest memory cost of the current iterations, but the absolute highest total time and time per request. The time issue is likely due to hugely unoptimized tokenization. - -### TrieRouter - -This is my first iteration of a PATRICIA trie router in PHP. I don't think it's currently perfect, as we could probably work on storing nodes as bytes rather than strings, but it's a good proof of concept for a tree based mechanism. +## Usage +Using the router is simple! The implementations here must adhere to the Router interface; ```php -Running 1000000 iterations -(100000 lookups) M: 4718.3 kb - T: 0.0581219196 s -(200000 lookups) M: 4718.3 kb - T: 0.1310830116 s -(300000 lookups) M: 4718.3 kb - T: 0.1909840107 s -(400000 lookups) M: 4718.3 kb - T: 0.2500770092 s -(500000 lookups) M: 4718.3 kb - T: 0.3067679405 s -(600000 lookups) M: 4718.3 kb - T: 0.3660039902 s -(700000 lookups) M: 4718.3 kb - T: 0.4237358570 s -(800000 lookups) M: 4718.3 kb - T: 0.4837160110 s -(900000 lookups) M: 4718.3 kb - T: 0.5422408581 s -Time: 0.6060788631 s -Avg/lookup: 0.0000006061 s +$router->add('GET', '/api/v1/:test', function($test) { + echo $test; +}); + +$router->lookup($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']); // ['code' => 200, 'handler' => callable, 'params' => []] ``` - -You can immediately see a ***huge*** time difference from SimpleRouter. Responses are in microseconds rather than milliseconds, but we're using 3x+ as much memory. From experimentation (and you can see this in the [visualization](tests/storage/trie/big.txt)) that the trie method creates a gigantic number of child elements to store the handler for every endpoint. - -### SegmentRouter - -This second iteration is the first to achieve the best of both worlds; lower memory usage and lower time per request! In order to achieve this, we simply split routes into segments and store each segment as a node. This means that there are no extraneous child elements and navigating to an endpoint requires less effort. The [visualization](tests/storage/segment/big.txt) also shows how much simpler the tree is compared to TrieRouter. - -```php -Running 1000000 iterations -(100000 lookups) M: 2891.8 kb - T: 0.0500328541 s -(200000 lookups) M: 2891.8 kb - T: 0.0995390415 s -(300000 lookups) M: 2891.8 kb - T: 0.1491589546 s -(400000 lookups) M: 2891.8 kb - T: 0.1987509727 s -(500000 lookups) M: 2891.8 kb - T: 0.2471258640 s -(600000 lookups) M: 2891.8 kb - T: 0.2962870598 s -(700000 lookups) M: 2891.8 kb - T: 0.3496289253 s -(800000 lookups) M: 2891.8 kb - T: 0.3990900517 s -(900000 lookups) M: 2891.8 kb - T: 0.4483740330 s -Time: 0.4971950054 s -Avg/lookup: 0.0000004973 s -``` - -Truly our most impressive show yet. By simplifying the structure of our tree and only storing what we need, we can achieve pretty incredible results in only 3 MB of RAM. +This API isn't expected to change, as all the router is expected to do is to look up our requested URI and return the handler and params for it. diff --git a/composer.json b/composer.json index 9086b0e..9c3e105 100644 --- a/composer.json +++ b/composer.json @@ -16,5 +16,10 @@ } ], "minimum-stability": "stable", - "require": {} + "require": {}, + "archive": { + "exclude": [ + "/tests" + ] + } } diff --git a/tests/test.php b/tests/test.php index 8f720b1..9a99e4d 100644 --- a/tests/test.php +++ b/tests/test.php @@ -2,8 +2,12 @@ require_once 'color.php'; require_once __DIR__ . '/../src/Router.php'; +require_once __DIR__ . '/../src/SegmentRouter.php'; -use Sharkk\Router\Router; +const ROUTES = __DIR__ . '/routes/'; +const TREES = __DIR__ . '/trees/'; + +use Sharkk\Router\SegmentRouter; // if there's a flag, reset the opcache if (in_array('-f', $argv)) { @@ -11,11 +15,11 @@ if (in_array('-f', $argv)) { echoTitle("opcache reset"); } -$r = new Router(); +$r = new SegmentRouter(); // Blog lookups -$blog = readAndAddRoutes('routes/blog.txt', $r); -writeRoutesToFile($r->routes, 'trees/blog.txt'); +$blog = readAndAddRoutes(ROUTES . 'blog.txt', $r); +writeRoutesToFile($r->routes, TREES . 'blog.txt'); echoTitle("Starting blog lookups"); runIterations(10000, $r, $blog); runIterations(100000, $r, $blog); @@ -24,8 +28,8 @@ unset($blog); // Github lookups $r->clear(); -$github = readAndAddRoutes('routes/github.txt', $r); -writeRoutesToFile($r->routes, 'trees/github.txt'); +$github = readAndAddRoutes(ROUTES . 'github.txt', $r); +writeRoutesToFile($r->routes, TREES . 'github.txt'); echoTitle("Starting github lookups"); runIterations(10000, $r, $github); runIterations(100000, $r, $github); @@ -34,8 +38,8 @@ unset($github); // Big lookups $r->clear(); -$big = readAndAddRoutes('routes/big.txt', $r); -writeRoutesToFile($r->routes, 'trees/big.txt'); +$big = readAndAddRoutes(ROUTES . 'big.txt', $r); +writeRoutesToFile($r->routes, TREES . 'big.txt'); echoTitle("Starting big lookups"); runIterations(10000, $r, $big); runIterations(100000, $r, $big); @@ -93,7 +97,7 @@ function echoTitle(string $title) { echo "\n"; } -function readAndAddRoutes(string $file, Router &$r): array +function readAndAddRoutes(string $file, &$r): array { $array = []; $routes = file($file);