forked from PHP/Router
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.
|
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
|
## 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.
|
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.
|
There are three tests performed, each running in iterations of 10000, 100000, 1000000;
|
||||||
|
- blog: 4 routes
|
||||||
Below are the results from our most rigorous tests; performing 1 million lookups on 1000 randomized routes with various lengths and parameters.
|
- github: 203 routes
|
||||||
|
- big: 1000 routes
|
||||||
### 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.
|
|
||||||
|
|
||||||
|
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
|
```php
|
||||||
Running 1000000 iterations
|
// CPU: 11th gen Intel Core i7-1185G7 @ 3.0GHz
|
||||||
(100000 lookups) M: 1846.2 kb - T: 32.6156370640 s
|
// RAM: 32GB 3200MHz DDR4
|
||||||
(200000 lookups) M: 1846.2 kb - T: 63.9784071445 s
|
🚀 Running 1000000 iterations...
|
||||||
(300000 lookups) M: 1846.2 kb - T: 96.9934570789 s
|
✔️ Done!
|
||||||
(400000 lookups) M: 1846.2 kb - T: 130.2443051338 s
|
Peak memory: 2726.5 kb
|
||||||
(500000 lookups) M: 1846.2 kb - T: 161.8348190784 s
|
Time: 1.5776410103 s
|
||||||
(600000 lookups) M: 1846.3 kb - T: 197.4232161045 s
|
Avg/lookup: 0.0000008231 s
|
||||||
(700000 lookups) M: 1846.1 kb - T: 231.8421580791 s
|
Shortest lookup: 0.0000009537 s
|
||||||
(800000 lookups) M: 1846 kb - T: 262.8337080479 s
|
Longest lookup: 0.0018200874 s
|
||||||
(900000 lookups) M: 1846.2 kb - T: 296.1434569359 s
|
|
||||||
Time: 330.9394941330 s
|
|
||||||
Avg/lookup: 0.0003309396 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.
|
## Usage
|
||||||
|
|
||||||
### 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.
|
|
||||||
|
|
||||||
|
Using the router is simple! The implementations here must adhere to the Router interface;
|
||||||
```php
|
```php
|
||||||
Running 1000000 iterations
|
$router->add('GET', '/api/v1/:test', function($test) {
|
||||||
(100000 lookups) M: 4718.3 kb - T: 0.0581219196 s
|
echo $test;
|
||||||
(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
|
$router->lookup($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']); // ['code' => 200, 'handler' => callable, 'params' => []]
|
||||||
(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
|
|
||||||
```
|
```
|
||||||
|
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.
|
||||||
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.
|
|
||||||
|
|
|
@ -16,5 +16,10 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"minimum-stability": "stable",
|
"minimum-stability": "stable",
|
||||||
"require": {}
|
"require": {},
|
||||||
|
"archive": {
|
||||||
|
"exclude": [
|
||||||
|
"/tests"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,12 @@
|
||||||
|
|
||||||
require_once 'color.php';
|
require_once 'color.php';
|
||||||
require_once __DIR__ . '/../src/Router.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 there's a flag, reset the opcache
|
||||||
if (in_array('-f', $argv)) {
|
if (in_array('-f', $argv)) {
|
||||||
|
@ -11,11 +15,11 @@ if (in_array('-f', $argv)) {
|
||||||
echoTitle("opcache reset");
|
echoTitle("opcache reset");
|
||||||
}
|
}
|
||||||
|
|
||||||
$r = new Router();
|
$r = new SegmentRouter();
|
||||||
|
|
||||||
// Blog lookups
|
// Blog lookups
|
||||||
$blog = readAndAddRoutes('routes/blog.txt', $r);
|
$blog = readAndAddRoutes(ROUTES . 'blog.txt', $r);
|
||||||
writeRoutesToFile($r->routes, 'trees/blog.txt');
|
writeRoutesToFile($r->routes, TREES . 'blog.txt');
|
||||||
echoTitle("Starting blog lookups");
|
echoTitle("Starting blog lookups");
|
||||||
runIterations(10000, $r, $blog);
|
runIterations(10000, $r, $blog);
|
||||||
runIterations(100000, $r, $blog);
|
runIterations(100000, $r, $blog);
|
||||||
|
@ -24,8 +28,8 @@ unset($blog);
|
||||||
|
|
||||||
// Github lookups
|
// Github lookups
|
||||||
$r->clear();
|
$r->clear();
|
||||||
$github = readAndAddRoutes('routes/github.txt', $r);
|
$github = readAndAddRoutes(ROUTES . 'github.txt', $r);
|
||||||
writeRoutesToFile($r->routes, 'trees/github.txt');
|
writeRoutesToFile($r->routes, TREES . 'github.txt');
|
||||||
echoTitle("Starting github lookups");
|
echoTitle("Starting github lookups");
|
||||||
runIterations(10000, $r, $github);
|
runIterations(10000, $r, $github);
|
||||||
runIterations(100000, $r, $github);
|
runIterations(100000, $r, $github);
|
||||||
|
@ -34,8 +38,8 @@ unset($github);
|
||||||
|
|
||||||
// Big lookups
|
// Big lookups
|
||||||
$r->clear();
|
$r->clear();
|
||||||
$big = readAndAddRoutes('routes/big.txt', $r);
|
$big = readAndAddRoutes(ROUTES . 'big.txt', $r);
|
||||||
writeRoutesToFile($r->routes, 'trees/big.txt');
|
writeRoutesToFile($r->routes, TREES . 'big.txt');
|
||||||
echoTitle("Starting big lookups");
|
echoTitle("Starting big lookups");
|
||||||
runIterations(10000, $r, $big);
|
runIterations(10000, $r, $big);
|
||||||
runIterations(100000, $r, $big);
|
runIterations(100000, $r, $big);
|
||||||
|
@ -93,7 +97,7 @@ function echoTitle(string $title) {
|
||||||
echo "\n";
|
echo "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
function readAndAddRoutes(string $file, Router &$r): array
|
function readAndAddRoutes(string $file, &$r): array
|
||||||
{
|
{
|
||||||
$array = [];
|
$array = [];
|
||||||
$routes = file($file);
|
$routes = file($file);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user