Update readme, exclude tests from package
This commit is contained in:
parent
a094bd0658
commit
5b30dea8fd
83
README.md
83
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.
|
||||
|
|
|
@ -16,5 +16,10 @@
|
|||
}
|
||||
],
|
||||
"minimum-stability": "stable",
|
||||
"require": {}
|
||||
"require": {},
|
||||
"archive": {
|
||||
"exclude": [
|
||||
"/tests"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue
Block a user