-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[FrameworkBundle] Improve performance of ControllerNameParser #20374
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
Conversation
// The second condition is for cases when this method is called with the | ||
// class::method string instead of a:b:c. It's important to fail early in this | ||
// case because calling findAlternative needlessly is bad for performance. | ||
if (3 !== count($parts) || $parts[1] === '') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Second check would be faster so IMO should be first ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't be first because $parts[1]
might not exist.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps check [0]
and [2]
as well for emptiness? As those are also not a valid "a:b:c" controller string
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we prefer yoda style: '' === $parts[1]
Just wondering: would it make sense to add this check in DelegatingLoader instead? --- a/src/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Routing/DelegatingLoader.php
@@ -21,7 +21,7 @@ use Psr\Log\LoggerInterface;
* DelegatingLoader delegates route loading to other loaders using a loader resolver.
*
* This implementation resolves the _controller attribute from the short notation
- * to the fully-qualified form (from a:b:c to class:method).
+ * to the fully-qualified form (from a:b:c to class::method).
*
* @author Fabien Potencier <[email protected]>
*/
@@ -85,7 +85,8 @@ class DelegatingLoader extends BaseDelegatingLoader
$this->loading = false;
foreach ($collection->all() as $route) {
- if ($controller = $route->getDefault('_controller')) {
+ $controller = $route->getDefault('_controller');
+ if ($controller && false === strpos($controller, '::')) {
try {
$controller = $this->parser->parse($controller);
} catch (\InvalidArgumentException $e) { |
@nicolas-grekas I can add that as well but the change in ControllerNameParser should be done regardless in my opinion. |
@nicolas-grekas @ro0NL All fixed. |
👍 |
1 similar comment
👍 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
if (3 !== count($parts = explode(':', $controller))) { | ||
$parts = explode(':', $controller); | ||
|
||
// The second condition is for cases when this method is called with the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the exception message is already straightforward now? Imo. no need for this comment.
@@ -85,7 +85,8 @@ public function load($resource, $type = null) | |||
$this->loading = false; | |||
|
|||
foreach ($collection->all() as $route) { | |||
if ($controller = $route->getDefault('_controller')) { | |||
$controller = $route->getDefault('_controller'); | |||
if ($controller && false === strpos($controller, '::')) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead, maybe add a comment here about skipping FQCN's for performance.
@ro0NL Like this? |
if ($controller = $route->getDefault('_controller')) { | ||
$controller = $route->getDefault('_controller'); | ||
// If $controller is in class::method notation already skip it for performance. | ||
if ($controller && false === strpos($controller, '::')) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about using a guard clause here to reduce the code's complexity?
if (!$controller || false !== strpos($controller, '::')) {
continue;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dunglas Good idea. I'll change it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
👍 |
I didn't notice the coding style error before. It's fixed now. Anything else? |
// unable to optimize unknown notation | ||
} | ||
$controller = $route->getDefault('_controller'); | ||
// If $controller is in class::method notation already skip it for performance. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know I suggested this change, but looking at it again, there is a potential BC break here since we have a new case where we won't call the parser. Let's be safe and revert (still keep the typo fix above). Most of the perf benefit in this PR doesn't come from this part of the patch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nicolas-grekas Understood, done.
👍 for this And should we apply it to older branches too ? We did it in the past for some performance improvements. On a side note, this will not improve the performance in prod, except for the cache warmup, as the routing loader is not called once anymore the matcher and generator are cached. |
Thank you @enumag. |
…ser (enumag) This PR was merged into the 2.7 branch. Discussion ---------- [FrameworkBundle] Improve performance of ControllerNameParser | Q | A | ------------- | --- | Branch? | 2.7 | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | | License | MIT | Doc PR | Today I was searching for bottlenecks in my application using Blackfire. And among other things I found one in Symfony. Blackfire showed that `Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser::findAlternative()` was called almost 300 times which took 28 miliseconds. It turns out that `Symfony\Bundle\FrameworkBundle\Routing\DelegatingLoader::load()` is calling `ControllerNameParser::parse()` without actually needing to do so because `$controller` is in the class::method notation already. `ControllerNameParser` threw an exception, DelegatingLoader caught and ignored it - that's ok. The problem is that generating the exception message took a lot of time because findAlternative is slow. In my case it called the levenshtein function over 5000 times which was completely useless because the exception is ignored anyway. Commits ------- cf333f3 [FrameworkBundle] Improve performance of ControllerNameParser
This one should not have been merged in 2.7. It does not fix a bug. This should have been merged in master instead (not even 3.2 at this stage). |
Today I was searching for bottlenecks in my application using Blackfire. And among other things I found one in Symfony. Blackfire showed that
Symfony\Bundle\FrameworkBundle\Controller\ControllerNameParser::findAlternative()
was called almost 300 times which took 28 miliseconds.It turns out that
Symfony\Bundle\FrameworkBundle\Routing\DelegatingLoader::load()
is callingControllerNameParser::parse()
without actually needing to do so because$controller
is in the class::method notation already.ControllerNameParser
threw an exception, DelegatingLoader caught and ignored it - that's ok. The problem is that generating the exception message took a lot of time because findAlternative is slow. In my case it called the levenshtein function over 5000 times which was completely useless because the exception is ignored anyway.