Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[Routing] Optimised dumped router matcher, prevent unneeded function calls.#21755

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Closed
frankdejonge wants to merge12 commits intosymfony:masterfromfrankdejonge:feature/optimize-routing-code
Closed

[Routing] Optimised dumped router matcher, prevent unneeded function calls.#21755

frankdejonge wants to merge12 commits intosymfony:masterfromfrankdejonge:feature/optimize-routing-code

Conversation

@frankdejonge
Copy link
Contributor

QA
Branch?master
Bug fix?no
New feature?no
BC breaks?no
Deprecations?no
Tests pass?yes
Fixed ticketsnone
LicenseMIT
Doc PRdoes not apply

The application I'm working on is fairly large. Because we had a routing issue (not caused by the framework) I looked through the dumped routing code. I spotted some easy wins. These changes brought down the time for thematch method to run from ~ 7.5ms to ~2.5ms. It's not a lot, but it's something. I've profiled it several times with blackfire to confirm. The results were very consistent. Mind you, our application has quite a serious amount of routes, a little over 900.

skoop, Capenus, GuilhemN, MarioBlazek, KennedyTedesco, mikemeier, ScullWM, CoolGoose, Koc, sschueller, and 8 more reacted with thumbs up emojitheofidry, linaori, and andrerom reacted with laugh emoji
}else {
$methods =implode("', '",$methods);
$code .=<<<EOF
if (!in_array(\$this->context->getMethod(), array('$methods'))) {
Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This removes N function calls.

if ($methods) {
if (1 ===count($methods)) {
$code .=<<<EOF
if (\$this->context->getMethod() != '$methods[0]') {
Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This removes N function calls.


if (!count($compiledRoute->getPathVariables()) &&false !==preg_match('#^(.)\^(?P<url>.*?)\$\1#'.(substr($regex, -1) ==='u' ?'u' :''),$regex,$m)) {
if ($supportsTrailingSlash &&substr($m['url'], -1) ==='/') {
$conditions[] =sprintf("rtrim(\$pathinfo, '/')=== %s",var_export(rtrim(str_replace('\\','',$m['url']),'/'),true));
Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This removed N trim calls.

foreach ($groupsas$collection) {
if (null !==$regex =$collection->getAttribute('host_regex')) {
if (!$fetchedHost) {
$code .="\$host =\$this->context->getHost();\n\n";
Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This removes a reference lookup, probably hardly any impact, but in optimised code, every cycle counts, right?

sstok reacted with thumbs up emoji
// bar
if (preg_match('#^/bar/(?P<foo>[^/]++)$#s',$pathinfo,$matches)) {
if (!in_array($this->context->getMethod(),array('GET','HEAD'))) {
if (!in_array($requestMethod,array('GET','HEAD'))) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Reading this line makes me think that we could even have$requestMethod set toGET when it'sHEAD, so that this condition can be simplified to justif ('GET' !== $requestMethod) { WDTY?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Wouldn't we then loose the ability to distinguish the request when there's only a HEAD route defined? I see in the there's a case for adding HEAD to the accepted methods when it's a GET request, but not the other way around. So if we only have a HEAD registered, we wouldn't be able to match that anymore, right? This would only be the case if you want to support defining separate HEAD handlers, I don't know if that's currently the case.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I'll just test this out real quick.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I just tested this out. We'd loose the ability to define HEAD routes when we make that additional optimisation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Sorry, I wasn't clear enough. My idea was to create a specific variable (different from$requestMethod, like$isLikeGetMethod or something) that holdsGET when the real method isHEAD.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

That shaved off a0.2ms, which is not a lot. Was done in this commit:0425f33

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I've implemented it in such a way that it keeps current behaviour, which also became more clear to me. If there's a HEAD registered after a GET for the same route, the first route will be matched. So now there is also a variable for the special "GET is also HEAD" case. The compiled route needs to remain aware whether there was actually a HEAD clause in there initially so it uses the right request method variable, the not "special" one.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I begin to wonder though, the code generating this, and executing it, become less easy to understand. Since the boost is so enormously tiny of that last bit I suggest to consider reverting that one commit and keep the rest.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I've added the tests for the other optimisation just in case. It's 03:00 here now, so bed time. Have a good one 👍

$request =$this->request;
$requestMethod =$isLikeGetMethod =$context->getMethod();

if ($requestMethod ==='HEAD') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Should beif ('HEAD' === $requestMethod) {

if (0 ===strpos($pathinfo,'/barhead') &&preg_match('#^/barhead/(?P<foo>[^/]++)$#s',$pathinfo,$matches)) {
if (!in_array($this->context->getMethod(),array('GET','HEAD'))) {
$allow =array_merge($allow,array('GET','HEAD'));
if ($isLikeGetMethod !='GET') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

if ('GET' !== $isLikeGetMethod) {

// baz5
if (preg_match('#^/test/(?P<foo>[^/]++)/$#s',$pathinfo,$matches)) {
if ($this->context->getMethod() !='POST') {
if ($isLikeGetMethod !='POST') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

if ('POST' !== $isLikeGetMethod) {

// baz.baz6
if (preg_match('#^/test/(?P<foo>[^/]++)/$#s',$pathinfo,$matches)) {
if ($this->context->getMethod() !='PUT') {
if ($isLikeGetMethod !='PUT') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

if ('PUT' !== $isLikeGetMethod) {

$request =$this->request;
$requestMethod =$isLikeGetMethod =$context->getMethod();

if ($requestMethod ==='HEAD') {
Copy link
Contributor

@hhamonhhamonFeb 27, 2017
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

if ('HEAD' === $isLikeGetMethod) {

if (0 ===strpos($pathinfo,'/barhead') &&preg_match('#^/barhead/(?P<foo>[^/]++)$#s',$pathinfo,$matches)) {
if (!in_array($this->context->getMethod(),array('GET','HEAD'))) {
$allow =array_merge($allow,array('GET','HEAD'));
if ($isLikeGetMethod !='GET') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

if ('GET' !== $isLikeGetMethod) {

// baz5
if (preg_match('#^/test/(?P<foo>[^/]++)/$#s',$pathinfo,$matches)) {
if ($this->context->getMethod() !='POST') {
if ($isLikeGetMethod !='POST') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

if ('POST' !== $isLikeGetMethod) {

// baz.baz6
if (preg_match('#^/test/(?P<foo>[^/]++)/$#s',$pathinfo,$matches)) {
if ($this->context->getMethod() !='PUT') {
if ($isLikeGetMethod !='PUT') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

if ('PUT' !== $isLikeGetMethod) {

@frankdejonge
Copy link
ContributorAuthor

frankdejonge commentedFeb 27, 2017
edited
Loading

@hhamon I get it, yoda-ing it now :P


// hey
if (rtrim($pathinfo,'/') ==='/multi/hey') {
if ($trimmedPathinfo ==='/multi/hey') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

if ('/multi/hey' === $trimmedPathinfo) {

$request =$this->request;
$requestMethod =$isLikeGetMethod =$context->getMethod();

if ($requestMethod ==='HEAD') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

if ('HEAD' === $requestMethod) {

@frankdejonge
Copy link
ContributorAuthor

@hhamon I've corrected the non-yoda to yoda statement, I've also corrected the ones which I didn't change into yoda statements.

\$requestMethod =\$isLikeGetMethod =\$context->getMethod();
if (\$requestMethod === 'HEAD') {
\$isLikeGetMethod = 'GET';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

the$isLikeGetMethod variable name makes me think it is a boolean

if (!in_array($this->context->getMethod(),array('GET','HEAD'))) {
$allow =array_merge($allow,array('GET','HEAD'));
if (!in_array($isLikeGetMethod,array('GET'))) {
$allow =array_merge($allow,array('GET'));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

not adding HEAD anymore to the allow array looks suspicious to me.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

@stof I'm open to suggestions here. The name was given by@fabpot initially, I couldn't come up with a better name for it now. The name goes into the realm of "normalised" pretty quickly which doesn't add a lot of context.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

@frankdejonge are you sure you replied to the right comment ? This looks unrelated

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

@stof I think github must have durped, because I looked at it when writing but now it's placed here. Super weird.

@frankdejonge
Copy link
ContributorAuthor

@stof I've corrected the case where only one method is present after filtering out the HEAD method.

if (!in_array($this->context->getMethod(),array('GET','HEAD'))) {
$allow =array_merge($allow,array('GET','HEAD'));
if ('GET' !==$isLikeGetMethod) {
$allow[] ='GET';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Why aren't you adding HEAD anymore here ? It is an allowed method

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

@stof this was an optimisation proposed by@fabpot. We stream HEAD in a special way (this is current behaviour). When a HEAD request is made, we also match GET. Inverted, when we declare GET actions, we also match in HEAD. So in this normalised case we know that if something is "like get" we don't need HEAD anymore because we're we already match GET.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Can we make sure that this is properly tested? If someone else breaks this, it will be a huge bug!

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

@Jean85 there's an additional test case added in this PR to do just that.

Jean85, rcousens, and CoolGoose reacted with thumbs up emoji
@frankdejonge
Copy link
ContributorAuthor

Perhaps it would be good to renameisGetLikeMethod tomethodWhereHeadMatchesGet? Might be a bit verbose, but in this case I think being explicit is preferable. WDYT@stof?

@frankdejonge
Copy link
ContributorAuthor

I've also added another test fixture which more clearly demonstrates the effect of the method optimisation.

@frankdejongefrankdejonge changed the titleOptimised dumped router matcher, prevent unneeded function calls.[Routing] Optimised dumped router matcher, prevent unneeded function calls.Feb 27, 2017
\$requestMethod =\$isLikeGetMethod =\$context->getMethod();
\$schema =\$context->getScheme();
if (\$requestMethod === 'HEAD') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

'HEAD' === 😉

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Fixed, thanks!

\$schema =\$context->getScheme();
if ('HEAD' ===\$requestMethod) {
\$isLikeGetMethod = 'GET';
Copy link
Member

@javiereguiluzjaviereguiluzFeb 28, 2017
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Theis prefix in this variable name looks wrong. Maybe it's too late, but what if we rename$isLikeGetMethod = 'GET' to$isSafeMethod = true

We had some discussions about what a "safe method" is in HTTP, but the section 9.1.1 of RFC 2616 mentionsGET andHEAD as safe methods:http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html


Or at least, rename$isLikeGetMethod = 'GET' to$isGetOrHead = true

Jean85 reacted with thumbs up emoji
Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Actually, we don't want a boolean, because then we can't perform a meaningful optimisation. We just want to have a interpreted method, which we can check against. Otherwise we'd need to do more check than less.

javiereguiluz reacted with thumbs up emoji

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

What about renaming it to$methodAlias or$canonicalMethod or something like that? We "can't" store a string in a$is* variable.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I likeconicalMethod, a lot. I had previously opted to usemethodWhereHeadMatchesGet instead. Which is a little verbose, but it does make it clear what's going on.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

+1 for$canonicalMethod

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I've renamed the variable to$canonicalMethod.

$code .=<<<EOF
if ('$methods[0]' !==\$$methodVariable) {
\$allow[] = '$methods[0]';
goto$gotoname;
Copy link
Contributor

@GuilhemNGuilhemNFeb 28, 2017
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Has the use of the goto ever been discussed?
It seems like here we only need to reverse the conditions to get ride of it.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I'm checking this out now, if proven to be effective I'll create another PR.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

@GuilhemN I suspected it to have some effect, but it did literally nothing.

@frankdejonge
Copy link
ContributorAuthor

Tests currently seem to be failing because master is broken.

\$context =\$this->context;
\$request =\$this->request;
\$requestMethod =\$canonicalMethod =\$context->getMethod();
\$schema =\$context->getScheme();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

should be$scheme here, not$schema, right?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

@fabpot correct, I've fixed it.

@fabpot
Copy link
Member

👍

@fabpot
Copy link
Member

Thank you@frankdejonge.

andrerom, dkvk, frankdejonge, dayofr, yceruto, duccoder, petrepatrasc, Renrhaf, epels, and 7ochem reacted with hooray emoji

@fabpotfabpot closed thisFeb 28, 2017
fabpot added a commit that referenced this pull requestFeb 28, 2017
…eeded function calls. (frankdejonge)This PR was squashed before being merged into the 3.3-dev branch (closes#21755).Discussion----------[Routing] Optimised dumped router matcher, prevent unneeded function calls.| Q             | A| ------------- | ---| Branch?       | master| Bug fix?      | no| New feature?  | no| BC breaks?    | no| Deprecations? | no| Tests pass?   | yes| Fixed tickets | none| License       | MIT| Doc PR        | does not applyThe application I'm working on is fairly large. Because we had a routing issue (not caused by the framework) I looked through the dumped routing code. I spotted some easy wins. These changes brought down the time for the `match` method to run from ~ 7.5ms to ~2.5ms. It's not a lot, but it's something. I've profiled it several times with blackfire to confirm. The results were very consistent. Mind you, our application has quite a serious amount of routes, a little over 900.Commits-------dd647ff [Routing] Optimised dumped router matcher, prevent unneeded function calls.
@fabpotfabpot mentioned this pull requestMay 1, 2017
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@fabpotfabpotfabpot left review comments

@javiereguiluzjaviereguiluzjaviereguiluz left review comments

@stofstofstof left review comments

+3 more reviewers

@hhamonhhamonhhamon left review comments

@GuilhemNGuilhemNGuilhemN left review comments

@Jean85Jean85Jean85 approved these changes

Reviewers whose approvals may not affect merge requirements

Assignees

No one assigned

Projects

None yet

Milestone

3.3

Development

Successfully merging this pull request may close these issues.

9 participants

@frankdejonge@fabpot@javiereguiluz@hhamon@stof@Jean85@GuilhemN@nicolas-grekas@carsonbot

[8]ページ先頭

©2009-2025 Movatter.jp