Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 940c29a

Browse files
committed
minor #21926 [Routing] Optimised dumped matcher (frankdejonge)
This PR was squashed before being merged into the 3.3-dev branch (closes #21926). Discussion ---------- [Routing] Optimised dumped matcher | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - TL;DR: I've optimised the PhpMatcherDumper output for a <del>60x</del> 4.4x performance improvement on a collection of ~800 routes by inducing cyclomatic complexity. [EDIT] The 60x performance boost was only visible when profiling with blackfire, which is quite possibly a result of the cost of profiling playing a part. After doing some more profiling the realistic benefit of the optimisation is more likely to be in the ranges is 1.3x to 4.4x. After the previous optimisation I began looking at how the PrefixCollection was adding its performance boost. I spotted another way to do this, which has the same theory behind it (excluding groups based on prefixes). The current implementation only groups when one prefix resides in the other. In this new implementation I've created a way to detect common prefixes, which allows for much more efficient grouping. Every time a route is added to the group it'll either merge into an existing group, merge into a new group with a route that has a common prefix, or merge into a new group with an existing group that has a common prefix. However, when a parameter is present grouping must only be done AFTER that route, this case is accounted for. In all other cases, where there's no collision routes can be grouped freely because if a group was matched other groups wouldn't have matched. After all the groups are created the groups are optimised. Groups with fewer than 3 children are inlined into the parent group. This is because a group with 2 children would potentially result in 3 prefix checks while if they are inlines it's 2 checks. Like with the previous optimisation I've profiled this using blackfire. But the match function didn't show up anymore. I've added `usleep` calls in the dumped matcher during profiling, which made it show up again. I've verified with @simensen that this is because the wall time of the function was too small for it to be of any interest. When it DID get detected, because of more tasks running, it would show up with around 250 nanoseconds. In comparison, the previous speed improvement brought the wall time down from 7ms to ~2.5ms on a set of ~800 routes. Because of the altered grouping behaviour I've not modified the PrefixCollection but I've created a new StaticPrefixCollection and updated the PhpMatcherDumper to use that instead. Commits ------- 449b691 [Routing] Optimised dumped matcher
2 parents f99dfb0 + 449b691 commit 940c29a

12 files changed

+745
-384
lines changed

src/Symfony/Component/Routing/Matcher/Dumper/DumperPrefixCollection.php

Lines changed: 0 additions & 107 deletions
This file was deleted.

src/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php

Lines changed: 31 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,6 @@ public function match(\$pathinfo)
134134
private function compileRoutes(RouteCollection $routes, $supportsRedirections)
135135
{
136136
$fetchedHost = false;
137-
138137
$groups = $this->groupRoutesByHostRegex($routes);
139138
$code = '';
140139

@@ -148,8 +147,8 @@ private function compileRoutes(RouteCollection $routes, $supportsRedirections)
148147
$code .= sprintf(" if (preg_match(%s, \$host, \$hostMatches)) {\n", var_export($regex, true));
149148
}
150149

151-
$tree = $this->buildPrefixTree($collection);
152-
$groupCode = $this->compilePrefixRoutes($tree, $supportsRedirections);
150+
$tree = $this->buildStaticPrefixCollection($collection);
151+
$groupCode = $this->compileStaticPrefixRoutes($tree, $supportsRedirections);
153152

154153
if (null !== $regex) {
155154
// apply extra indention at each line (except empty ones)
@@ -164,37 +163,51 @@ private function compileRoutes(RouteCollection $routes, $supportsRedirections)
164163
return $code;
165164
}
166165

166+
private function buildStaticPrefixCollection(DumperCollection $collection)
167+
{
168+
$prefixCollection = new StaticPrefixCollection();
169+
170+
foreach ($collection as $dumperRoute) {
171+
$prefix = $dumperRoute->getRoute()->compile()->getStaticPrefix();
172+
$prefixCollection->addRoute($prefix, $dumperRoute);
173+
}
174+
175+
$prefixCollection->optimizeGroups();
176+
177+
return $prefixCollection;
178+
}
179+
167180
/**
168-
* Generates PHP code recursively to match a tree of routes.
181+
* Generates PHP code to match a tree of routes.
169182
*
170-
* @param DumperPrefixCollection $collection A DumperPrefixCollection instance
183+
* @param StaticPrefixCollection $collection A StaticPrefixCollection instance
171184
* @param bool $supportsRedirections Whether redirections are supported by the base class
172-
* @param string $parentPrefix Prefix of the parent collection
185+
* @param string $ifOrElseIf Either "if" or "elseif" to influence chaining.
173186
*
174187
* @return string PHP code
175188
*/
176-
private function compilePrefixRoutes(DumperPrefixCollection $collection, $supportsRedirections, $parentPrefix = '')
189+
private function compileStaticPrefixRoutes(StaticPrefixCollection $collection, $supportsRedirections, $ifOrElseIf = 'if')
177190
{
178191
$code = '';
179192
$prefix = $collection->getPrefix();
180-
$optimizable = 1 < strlen($prefix) && 1 < count($collection->all());
181-
$optimizedPrefix = $parentPrefix;
182-
183-
if ($optimizable) {
184-
$optimizedPrefix = $prefix;
185193

186-
$code .= sprintf(" if (0 === strpos(\$pathinfo, %s)) {\n", var_export($prefix, true));
194+
if (!empty($prefix) && '/' !== $prefix) {
195+
$code .= sprintf(" %s (0 === strpos(\$pathinfo, %s)) {\n", $ifOrElseIf, var_export($prefix, true));
187196
}
188197

189-
foreach ($collection as $route) {
190-
if ($route instanceof DumperCollection) {
191-
$code .= $this->compilePrefixRoutes($route, $supportsRedirections, $optimizedPrefix);
198+
$ifOrElseIf = 'if';
199+
200+
foreach ($collection->getItems() as $route) {
201+
if ($route instanceof StaticPrefixCollection) {
202+
$code .= $this->compileStaticPrefixRoutes($route, $supportsRedirections, $ifOrElseIf);
203+
$ifOrElseIf = 'elseif';
192204
} else {
193-
$code .= $this->compileRoute($route->getRoute(), $route->getName(), $supportsRedirections, $optimizedPrefix)."\n";
205+
$code .= $this->compileRoute($route[1]->getRoute(), $route[1]->getName(), $supportsRedirections, $prefix)."\n";
206+
$ifOrElseIf = 'if';
194207
}
195208
}
196209

197-
if ($optimizable) {
210+
if (!empty($prefix) && '/' !== $prefix) {
198211
$code .= " }\n\n";
199212
// apply extra indention at each line (except empty ones)
200213
$code = preg_replace('/^.{2,}$/m', ' $0', $code);
@@ -387,7 +400,6 @@ private function compileRoute(Route $route, $name, $supportsRedirections, $paren
387400
private function groupRoutesByHostRegex(RouteCollection $routes)
388401
{
389402
$groups = new DumperCollection();
390-
391403
$currentGroup = new DumperCollection();
392404
$currentGroup->setAttribute('host_regex', null);
393405
$groups->add($currentGroup);
@@ -405,30 +417,6 @@ private function groupRoutesByHostRegex(RouteCollection $routes)
405417
return $groups;
406418
}
407419

408-
/**
409-
* Organizes the routes into a prefix tree.
410-
*
411-
* Routes order is preserved such that traversing the tree will traverse the
412-
* routes in the origin order.
413-
*
414-
* @param DumperCollection $collection A collection of routes
415-
*
416-
* @return DumperPrefixCollection
417-
*/
418-
private function buildPrefixTree(DumperCollection $collection)
419-
{
420-
$tree = new DumperPrefixCollection();
421-
$current = $tree;
422-
423-
foreach ($collection as $route) {
424-
$current = $current->addPrefixRoute($route);
425-
}
426-
427-
$tree->mergeSlashNodes();
428-
429-
return $tree;
430-
}
431-
432420
private function getExpressionLanguage()
433421
{
434422
if (null === $this->expressionLanguage) {

0 commit comments

Comments
 (0)