Skip to content

[DI] generate preload.php file for PHP 7.4 in cache folder #32032

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

Merged
merged 1 commit into from
Sep 8, 2019

Conversation

nicolas-grekas
Copy link
Member

@nicolas-grekas nicolas-grekas commented Jun 13, 2019

Q A
Branch? 4.4
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets #29105
License MIT
Doc PR -

This PR makes the PhpDumper generate a preloading file suited for PHP 7.4.
On a skeleton app, the generated file is var/cache/dev/srcApp_KernelDevDebugContainer.preload.php (of course, this varies by env name + kernel class)

One missing thing is listing some classes that are always needed but are not related to services.

Typically: Request and Response. We might need a new mechanism to make this list extensible.

I did not measure the benefit of this on PHP 7.4. I would really appreciate if someone could give it a try on PHP 7.4 with preloading enabled.

@nicolas-grekas nicolas-grekas force-pushed the preload branch 2 times, most recently from 85e1fec to e2b7dce Compare June 14, 2019 14:18
@nicolas-grekas
Copy link
Member Author

For reference, here is some previous work by @BenMorel that shows that loading only the hot classes (vs everything) is what provides the best boost. Identifying these hot classes is what I'm trying to achieve here.

@teohhanhui
Copy link
Contributor

Is it really worth it to preload only hot classes? Doesn't that increase the complexity a lot, vs just preloading everything?

@BenMorel
Copy link
Contributor

There's a slight performance improvement vs preloading everything when preloading hot classes that were selected using actual runtime data, as reported by opcache after a few runs of the application:

Benchmark Requests per second Diff
No preloading 631 rq/s -
Preload hot classes 738 rq/s +17%
Preload everything 712 rq/s +13%

(original benchmark in this thread)

Any other pre-computed list would need to be benchmarked again, as the results may be very different (and could even be slower than preloading everything, as the list may be missing classes that will need to be linked at runtime).

All in all, the difference between a carefully optimized preloader and an eager preloader is small, I'd personally be in favour of preloading everything by default, and let users create their own preloading script if they need the extra few % of performance.

What could be interesting however, is shipping Symfony with a tool (like the one I used in this post) to generate the preloading file from opcache, together with some instructions on how to:

  • restart the server
  • navigate the website / use as many features of the app as possible
  • then dump the preloading script

@nicolas-grekas
Copy link
Member Author

Identifying the hot classes using runtime data from opcache has a big drawback: rebooting the fpm pool on prod servers. Not sure it's calling for good ops practices.
Without having working numbers, it's way too early to draw any conclusions or close any doors.
You may be right though, but please allow me to do the experiment :)

@BenMorel
Copy link
Contributor

I did not have in mind to gather runtime data from the prod environment, but from the dev one!

No worries, I just wanted to give a bit of warning here, I'm looking forward to your numbers!

@nicolas-grekas nicolas-grekas force-pushed the preload branch 5 times, most recently from aa39efe to 8aa4aca Compare June 17, 2019 12:54
@k00ni
Copy link

k00ni commented Jun 18, 2019

I am not sure, if this is the right place to ask.

In the related RFC they say:

The traded-in flexibility would include the inability to update these files once the server has been started (updating these files on the filesystem will not do anything; A server restart will be required to apply the changes); And also, this approach will not be compatible with servers that host multiple applications, or multiple versions of applications [...]

How do you plan to manage updates in the vendor folder, if you have to restart the server to apply changes? If i am on a shared hosting environment, would that affect me when using Symfony in the future?

@nicolas-grekas
Copy link
Member Author

Preloading is fundamentally incompatible with shared hosting yes, so you wouldn't use this on your provider. The added file is totally opt-in so one won't use it if one isn't able to restart the FPM server when deploying new code, that's correct.

@BenMorel
Copy link
Contributor

BenMorel commented Jun 18, 2019

If am on a shared hosting environment, would that affect me when using Symfony in the future?

Nope, preloading requires setup at server level (opcache.preload), which will never be enabled on shared hosting environments.

Most likely any preloading script shipped with Symfony or Composer will be opt-in, and the choice to include it or not will be left to the server administrator.

@teohhanhui
Copy link
Contributor

teohhanhui commented Jun 18, 2019

It's 2019, so hopefully the population of developers who unfortunately still have to deal with shared hosting is shrinking rapidly...

fabpot added a commit that referenced this pull request Jun 20, 2019
…grekas)" (nicolas-grekas)

This PR was merged into the 4.4 branch.

Discussion
----------

Revert "minor #32054 Prepare for PHP 7.4 preload (nicolas-grekas)"

| Q             | A
| ------------- | ---
| Branch?       | 4.4
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #32032
| License       | MIT
| Doc PR        | -

This reverts commit a0aa941, reversing
changes made to 8496003.

This change is incompatible with Composer's class map generation.

Commits
-------

8a1813a Revert "minor #32054 Prepare for PHP 7.4 preload (nicolas-grekas)"
@tsantos84
Copy link
Contributor

tsantos84 commented Jun 26, 2019

Hi @nicolas-grekas, if we have this feature in composer we could configure the classes directly on composer.json file and it could work for vendor/package level as well. Makes sense?

HttpFoundation composer.json file:

{
    "preload": [
         "Symfony\\HttpFoundation\\{Request,Response}",
         "Symfony\\EntireNamespace\\*",
         "Symfony\\{NS1,NS2}\\*"
    ]
}

@tsantos84
Copy link
Contributor

I've just read the RFC at composer/composer#7777 and seems that is not an easy decision to put such feature on composer.

@nicolas-grekas nicolas-grekas force-pushed the preload branch 5 times, most recently from 517bd3e to 5d119d6 Compare September 8, 2019 15:45
@nicolas-grekas
Copy link
Member Author

I rebased this PR and I think it's time to merge it.

Preloading doesn't work yet, but that's on PHP's side.
The patch also fines tune the generated container even when preloading is not in use.

Status: needs review

@fabpot
Copy link
Member

fabpot commented Sep 8, 2019

Thank you @nicolas-grekas.

fabpot added a commit that referenced this pull request Sep 8, 2019
…lder (nicolas-grekas)

This PR was merged into the 4.4 branch.

Discussion
----------

[DI] generate preload.php file for PHP 7.4 in cache folder

| Q             | A
| ------------- | ---
| Branch?       | 4.4
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #29105
| License       | MIT
| Doc PR        | -

This PR makes the PhpDumper generate a preloading file suited for PHP 7.4.
On a skeleton app, the generated file is `var/cache/dev/srcApp_KernelDevDebugContainer.preload.php` (of course, this varies by env name + kernel class)

One missing thing is listing some classes that are always needed but are not related to services.

Typically: `Request` and `Response`. We might need a new mechanism to make this list extensible.

I did not measure the benefit of this on PHP 7.4. I would really appreciate if someone could give it a try on PHP 7.4 with preloading enabled.

Commits
-------

c4dad0d [DI] generate preload.php file for PHP 7.4 in cache folder
@fabpot fabpot merged commit c4dad0d into symfony:4.4 Sep 8, 2019
@@ -522,13 +553,17 @@ private function addServiceInclude(string $cId, Definition $definition): string
}

foreach (array_diff_key(array_flip($lineage), $this->inlinedRequires) as $file => $class) {
$file = preg_replace('#^\\$this->targetDirs\[(\d++)\]#', sprintf('\dirname(__DIR__, %d + $1)', $this->asFiles), $file);
Copy link
Member

Choose a reason for hiding this comment

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

why generating code using $this->targetDirs first, to replace it later with dirname(__DIR__) ? We would generate the right code directly.

Copy link
Member Author

@nicolas-grekas nicolas-grekas Sep 10, 2019

Choose a reason for hiding this comment

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

I prefer not changing this when it's not needed: the code starts with
$this->targetDirs[0] = \dirname($containerDir): target dirs are dynamic. This is used when calling cache clear and is stable, no reason to change.
Using dirname() is really useful when referencing files in the src/ or in the vendor/ folder, which is where it is used.
Lowest risk strategy :)

Copy link
Member Author

Choose a reason for hiding this comment

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

Second look, I did something, please check #33529 :)

@@ -1285,7 +1320,7 @@ protected function {$methodNameAlias}()
return $code;
}

private function addInlineRequires(): string
private function addInlineRequires(?array &$preload): string
Copy link
Member

Choose a reason for hiding this comment

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

should null be allowed ?

Copy link
Member Author

Choose a reason for hiding this comment

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

to allow calling the method with an uninitialized variable

$c = $p->getDefaultValueConstantName();

if ($i = strpos($c, '::')) {
self::doPreload(substr($c, 0, $i));
Copy link
Member

Choose a reason for hiding this comment

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

if you reuse the same class multiple types in different parameters, wouldn't this preload it multiple times ? Also, it would not register it in $preloaded in the main function, and so it will be processed once again by the main function.

Copy link
Member Author

Choose a reason for hiding this comment

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

Indeed, fixed in #33525

@steevanb
Copy link

@nicolas-grekas As soon as PHP 7.4 will be available, I will benchmark with and without preloading and add results on phpbenchmarks.com ;)

@bradjones1
Copy link
Contributor

@steevanb Is there a benchmark on preloading per your note above? I looked on the site and didn't find anything specific, but maybe I just missed it.

@steevanb
Copy link

@bradjones1 for now PHP 7.4 is not added on phpbenchmarks.com, cause i'm on a new version of benchmarks and site who will be released in a few weeks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.