Symfony 4 is the fastest PHP framework according to independent benchmarks, but we are continuously working on making it faster. In Symfony 4.1, we improved the Routing component to make it much faster when matching incoming URLs.
In web applications, routing is divided in two main operations: generation, which generates a URL from the given route and parameters; and matching, which decides which PHP code (i.e. controller) is executed as the response of an incoming URL.
In order to speed up the application, during the compilation phase Symfony generates a PHP class called "matcher" which contains all the route definitions optimized to match incoming URLs. For example, this is a snippet of the class generated for the Symfony Demo application:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
// var/cache/prod/srcProdDebugProjectContainerUrlMatcher.php
class srcProdDebugProjectContainerUrlMatcher
{
// ...
public function match($rawPathinfo)
{
// ...
// blog_post
if (preg_match('#^/(?P<_locale>en|fr|de|es)/blog/posts/(?P<slug>[^/]++)$#s', $pathinfo, $matches)) {
if ('GET' !== $canonicalMethod) {
$allow[] = 'GET';
goto not_blog_post;
}
return $this->mergeDefaults(array_replace($matches, array('_route' => 'blog_post')), array ( '_controller' => 'App\\Controller\\BlogController::postShow', '_locale' => 'en',));
}
not_blog_post:
// ...
}
}
In Symfony 4.1 we refactored the matcher class generator based on the ideas shared in the following article: Fast request routing using regular expressions. The article explains the technique used by FastRoute, a routing library created by the genius PHP contributor Nikita Popov.
The basic idea is to avoid making separate preg_match()
calls for each route
and instead, combine all regular expressions into a single regular expression.
We also made many other big and small optimizations. If you are curious, see
the Pull Request #26059 and #26169 for all the details.
All in all, using the same Symfony Demo application example, this is how the
blog_post
route is matched now:
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
$regexList = array(
0 => '{^(?'
.'|/(en|fr|de|es)/admin/post/?(*:82)'
.'|/(en|fr|de|es)/admin/post/new(*:166)'
.'|/(en|fr|de|es)/admin/post/(\\d+)(*:253)'
.'|/(en|fr|de|es)/admin/post/(\\d+)/edit(*:345)'
.'|/(en|fr|de|es)/admin/post/([^/]++)/delete(*:442)'
.'|/(en|fr|de|es)/blog/?(*:519)'
.'|/(en|fr|de|es)/blog/rss\\.xml(*:603)'
.'|/(en|fr|de|es)/blog/page/([1-9]\\d*)(*:694)'
.'|/(en|fr|de|es)/blog/posts/([^/]++)(*:784)'
.'|/(en|fr|de|es)/blog/comment/([^/]++)/new(*:880)'
.'|/(en|fr|de|es)/blog/search(*:962)'
.'|/(en|fr|de|es)/login(*:1038)'
.'|/(en|fr|de|es)/logout(*:1116)'
.'|/(en|fr|de|es)?(*:1188)'
.')$}sD',
);
foreach ($regexList as $offset => $regex) {
// ...
default:
$routes = array(
// ...
784 => array(array('_route' => 'blog_post', '_controller' => 'App\\Controller\\BlogController::postShow', '_locale' => 'en'), array('_locale', 'slug'), array('GET' => 0), null),
);
// ...
}
In practice, combining all regular expressions improves URL matching performance by almost two orders of magnitude. In our benchmarks, Symfony 4.1 URL matching is 77 times faster than in previous Symfony versions. This also means that Symfony 4.1 router is now the fastest PHP router, beating FastRoute and all the other routing libraries.
Best of all: you don't need to make any change in your application to use this fast router. You just need to upgrade to Symfony 4.1 when it's released at the end of May 2018. Meanwhile you can test it in your applications and report any issue that you find.
Update: Nicolas has published two technical articles explaining in detail how the new router works:
Is there any chance this can be back ported into 3.4?
@David sorry but we can't do that because, according to our release policy, new features can only be merged in the master branch. In this case, porting it back would be even more difficult because this is a really complex feature.
So, routes order matter for best performance?
If yes, we will able to specify what routes are more "important" (such as home page, front site, and at the end, back-office site) ?
@Thomas yes, the route order matters for best performance (before these changes too). That's why the Symfony Profiler shows in the Routing panel the number of routes tested before matching the URL. If you routes allow it, you should always reorder them for best performance.
@ Thomas Decaux: The performance of the Routing component is already high enough that any ordering optimization shouldn't be noticed. Unless you can provide a good a example where this is not the case :)
What that last part "(*:...)" mean?
I mean, what is the purpose of this regex?
For those who are curious like me: http://www.rexegg.com/backtracking-control-verbs.html#mark
Nice. Any examples of how much this matters in real-world applications? In other words, are we closer to 1 millisecond going down to 1/77th of a millisecond (doesn't matter), or 77 milliseconds down to 1 millisecond (matters a lot)?
Thank you Marcos ;-)
@Yngve it all depends on your application, because the routing config can vary from being trivial to extremely large and complex. But you are right about the general idea: the routing part will never be the slowest part of your application.
@Javier Looking at the PRs though this is a refactor of an existing feature, unless I've missed something. I don't see any BC breaks.
I wrote this if you want to know more about this change: https://medium.com/@nicolas.grekas/making-symfonys-router-77-7x-faster-1-2-958e3754f0e1
@Sebastiaan Stok that exactly what benchmark demonstrates ... first against last routes. There is a difference.