-
Notifications
You must be signed in to change notification settings - Fork 11.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[7.x] Refactor route caching #31188
[7.x] Refactor route caching #31188
Conversation
Define "almost no difference" and what % you're looking for, and in what circumstances. Do you have any benchmark code for people to test this out and try to improve? |
7e8c990
to
9cb7657
Compare
@36864 definitely. I used the Here's a benchmark for 800 dynamic routes:
<?php
Route::get('/', 'Controller@index');
for ($i = 0; $i < 800; $i++) {
Route::get("/foo/{bar}/$i", 'Controller@index')->name("non-static-$i");
} Benchmark for current cached routing returns 269 requests per second: View benchmark$ ab -t 10 -c 10 http://test-routes.test/foo/wrwer/799
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking test-routes.test (be patient)
Finished 2691 requests
Server Software: nginx/1.17.7
Server Hostname: test-routes.test
Server Port: 80
Document Path: /foo/wrwer/799
Document Length: 2426 bytes
Concurrency Level: 10
Time taken for tests: 10.001 seconds
Complete requests: 2691
Failed requests: 0
Total transferred: 9127040 bytes
HTML transferred: 6528366 bytes
Requests per second: 269.08 [#/sec] (mean)
Time per request: 37.164 [ms] (mean)
Time per request: 3.716 [ms] (mean, across all concurrent requests)
Transfer rate: 891.23 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.0 0 0
Processing: 19 37 34.9 33 1582
Waiting: 19 37 34.9 33 1581
Total: 19 37 34.9 33 1582
Percentage of the requests served within a certain time (ms)
50% 33
66% 38
75% 41
80% 43
90% 50
95% 55
98% 65
99% 72
100% 1582 (longest request) Benchmark for new cached routing returns 221 requests per second: View benchmark$ ab -t 10 -c 10 http://test-routes-caching.test/foo/ewrrre/799 ~/Sites/test-routes-caching
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking test-routes-caching.test (be patient)
Finished 2215 requests
Server Software: nginx/1.17.7
Server Hostname: test-routes-caching.test
Server Port: 80
Document Path: /foo/ewrrre/799
Document Length: 2426 bytes
Concurrency Level: 10
Time taken for tests: 10.003 seconds
Complete requests: 2215
Failed requests: 0
Total transferred: 7512576 bytes
HTML transferred: 5373590 bytes
Requests per second: 221.44 [#/sec] (mean)
Time per request: 45.160 [ms] (mean)
Time per request: 4.516 [ms] (mean, across all concurrent requests)
Transfer rate: 733.44 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.0 0 0
Processing: 12 45 99.1 32 2512
Waiting: 12 45 99.1 32 2512
Total: 12 45 99.1 32 2513
Percentage of the requests served within a certain time (ms)
50% 32
66% 35
75% 38
80% 39
90% 50
95% 85
98% 244
99% 280
100% 2513 (longest request)
90% 50
95% 55
98% 65
99% 72
100% 1582 (longest request) What must be noted is that I get widely different results with |
Thanks. I'll see if I can dig into this tonight. Looks like the new system is slower on average, but has a slightly faster best-case. It might be worth benchmarking cases with different number of routes to determine if the changes made have an impact on every app or only those with many routes (although I guess if an app only has 5 routes no amount of fiddling with the caching mechanism would have a significant impact) As for the random variance you're getting, try reducing the concurrency level for |
Did you try to use json_encode/json_decode instead of serialize/unserialize? |
Don't know, for me the new version is faster (ran multiple times): Windows 10, PHP 7.4.1, Apache Laravel 6
Laravel 7 refactor-route-caching
|
No. I don't think that'll be any faster though?
Thanks for testing! Did you try running the benchmarks a couple of times to see if you get similar results? |
Yeah, around 10 times each, with opcache and without, new version was always faster by 30+ requests/s. |
In very simple tests, json_decode is faster, but, with complex and larger objects, json_decode is more slow than unserialize. Really, will not bring improvements. |
Sorry for the noob question, but could anyone provide me with a small app that would run this PR and allow me to run the |
@gocanto thanks, that's the parts I figured out, but then, I don't know what to do, which files to patch, how to compile the routes, etc :) |
Once you have this branch pulled, you'll need to empty the contents of Once you've done that, run |
@nicolas-grekas I see. If you want to compile the After this is done, you should be able to see a compiled routes file within your |
After typing out all the steps I realized it would have been easier to just upload my test project. Here you go: https://github.com/36864/laravel-route-cache-test Just clone that, run |
Thanks @36864 that helped a lot! So here are my quick findings: The biggest perf hog is the call to With the current state of the PR, I'm at 74 req/s. If I comment the call to Then, if I switch to using My reco would be to use For |
Another problem that I found in current route cache implementation is the high RAM consume in a request when system has a lot of routes (1000+). It increments about 10MB of RAM by each request. Currently, I needed to disable route cache when production because this problem. Will this problem be solved? Maybe split cache files by first character may be resolve the problem. |
@nicolas-grekas thank you for helping out here! Some thoughts on your feedback:
Hmm, technically it's not possible that the current PR will work without the call. If I comment it out myself I get a 404 when I visit any route but I do get improved results from the
If I use var_export for the regular routes I get the following error:
I guess you implemented this method on the RouteCollection?
I've just pushed a commit with switching to var_export for
I'll try to look into this tomorrow if that's possible. One more question: what exact benchmark command are you using? Thanks again for helping out! |
@taylorotwell let me know that with the latest version he isn't seeing improvements himself. |
Actually I didn't, I just commented out the call :) But that's definitely the next thing to optimize. Could be by implementing this method, or by creating a new
It was |
Was opcache enabled? |
I'm assuming opcache would execute the base64 decode ahead of time, if enabled, which could explain why you and Dries were seeing some differences. |
It was enabled for me, but it would be totally new knowledge to me if opcache were to resolve b64 ahead of time. What do you base this idea on? |
Opcache already does constant folding and some ahead of time evaluations, like is_string on a value that is known to be a string. I wouldn't be surprised if it did the base64 decode a head of time, but I've not checked the source. If it doesn't... maybe it should. ;) |
For reference, here is what I know on the topic, with links to the source: PHP-CS-Fixer/PHP-CS-Fixer#3048 |
I also have opcache enabled.
Hmm, since
Hmm this was the path I took first but it overcomplicated the router (because it needs to have the I really wonder why there's so much difference in the test results between yours and mine. With your |
I think I finally managed to crack it. I ditched the setRoutes call like you suggested @nicolas-grekas and I'm now finally seeing much better results. I still need to find a way to lazy-load the routes when I'll pick this up on monday. |
Verified that the fallback issue isn't present in this pr. Think except for tests that we're pretty much there then. @nicolas-grekas I'm wondering if it makes a difference if we just returned an array from the compiled routes file and do the |
Not preferences in terms of opcache: both ways would provide equal opcache-ability. As fits best for you. |
4280851
to
2673a27
Compare
2673a27
to
e7ac4f5
Compare
Thank you ALL for helping getting this merged in! Especially @nicolas-grekas and @frankdejonge 👏👏 |
* upstream/master: (78 commits) [7.x] Implement anonymous components (laravel#31363) Apply fixes from StyleCI (laravel#31480) Apply fixes from StyleCI (laravel#31479) revert broken table feature move files add test for event payload of type object (laravel#31477) [7.x] Refactor route caching (laravel#31188) [6.x] Fixes appendRow on console table (laravel#31469) Apply fixes from StyleCI (laravel#31474) formatting Throw exception on empty collection (laravel#31471) Add tests for Query Builder when array value given (laravel#31464) Remove addHidden method (laravel#31463) allow afterResponse chain add getRawOriginal Remove unused use-statement Update comment [6.x] Change MySql nullable modifier to allow generated columns to be not null (laravel#31452) [6.x] Test for pushed events (laravel#31451) Fixed phpdoc ...
Hi @driesvints But, now you can't set your own validators.... Route::$validators = [
new MethodValidator,
new SchemeValidator,
new HostValidator,
new UriValidator,
]; This PR completely breaks my code if I cached the routes. Can we have a change to choose one of them? |
@yaquawa this was never documented or publicly supported sorry. You can technically still use that (although I don't recommend to) if you don't use route caching. |
Hi @driesvints, but I don't think it's a good idea to change the behavior of how router works just because caching is applied or not. Why not just use one of them? Two implementations work together feels so weird. |
@yaquawa sorry we have no plans to change this. |
@driesvints The try block always thrown because Could you give me some information about this? Thanks. |
@driesvints By the way, do you know any other ways to use my own "match strategy" against the request after this PR? |
To handle dynamic route arguments properly. We have tests to back that.
The framework supports dynamically added routes at runtime. This collection holds those routes.
Not at the moment no. You can still use it as long as you don't use route caching (afaik). |
But I think the "dynamic" ones will never get checked through the |
We have tests that verify exactly that. |
The past couple of weeks I've been trying to update the route caching system by using the new Symfony
CompiledUrlMatcherDumper
andCompiledUrlMatcher
in order to get faster route resolving. Unfortunately, the benchmarks that I did so far haven't been very promising. The speed of route resolving vs the old system has almost no difference. By opening a PR here I'm hoping to see if anyone's able to chip in and help out to see if there's anything I'm missing or doing wrong. I've left some comments on the PR itself on some of the parts I'm doubting or where I believe we could do better.Would love to get some feedback here 🙏
Big thanks to @frankdejonge for the initial inspiration with his work on this! https://github.com/frankdejonge/framework/pull/1