-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[DI] Generate one file per service factory #23678
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
} | ||
|
||
if (null === $methodName) { | ||
throw new \InvalidArgumentException(sprintf('Missing name of method to call to construct the service "%s".', $id)); | ||
} | ||
|
||
$factoryCall = sprintf($definition->isPublic() || !method_exists(ContainerBuilder::class, 'addClassResource') ? '$container->%s(false)' : '%s::create($container, false)', $methodName); |
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.
passing the factory class instead of the method name may need to be done another way to preserve BC
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.
fixed via #23693
@@ -456,3 +498,20 @@ protected function getDefaultParameters() | |||
); | |||
} | |||
} | |||
|
|||
final class ProjectServiceContainer_FactorySimpleService extends ProjectServiceContainer |
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.
here is the kind of class that this generates
a714810
to
736bc22
Compare
About this:
I can see added complexity in three ways:
So, which are the benefits that I'm missing i this proposal? Thanks! |
It's a guess, so if you're wrong, you have your benefit, there is no other. |
Sounds interesting. Is this PR functional? I have access to a few fairly large (monolithic) applications I'd be happy to test this against. |
528afec
to
e709ccf
Compare
@robfrawley not ready yet, I'll report back :) |
e5c35a7
to
88aea90
Compare
…umperInterface::getProxyFactoryCode() (nicolas-grekas) This PR was merged into the 3.4 branch. Discussion ---------- [DI][ProxyManager] Pass the factory code to execute to DumperInterface::getProxyFactoryCode() | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | yes | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - Passing the full code to call the factory is more flexible, as spotted while working on #23678. Commits ------- 0754617 [DI][ProxyManager] Pass the factory code to execute to DumperInterface::getProxyFactoryCode()
dc0fc39
to
9628fbe
Compare
@ogizanagi should be fixed (lazy proxies are not split anymore because that needs more work - another PR maybe.) |
1b81099
to
e91ba87
Compare
e91ba87
to
359b6ef
Compare
So I tried in a real-life app, and here are some new blackfire profile comparisons:
No significative diff at all it seems :/ In addition, note the |
3.4 or 4.0? What about testing using ab? |
Using Symfony 4.0. Tests using ab do not show any noteworthy difference either. |
OPCache needs to copy from SHM to current process local memory all the classes it parses. This process is highly optimized in OPCache, we even designed an SSE2 SIMD process for part of such a work (https://github.com/php/php-src/blob/PHP-7.1/ext/opcache/zend_accelerator_util_funcs.c#L605) But it will still eats some little CPU cycles, and above all, will eat "a lot" of memory because a class is really a heavy stuff into PHP in term of memory footprint (much more than an object). All this process is detailed in an article I once wrote (targeting PHP 5) at http://jpauli.github.io/2015/03/05/opcache.html Source code parts are available at https://github.com/php/php-src/blob/PHP-7.1/ext/opcache/zend_accelerator_util_funcs.c#L561 and https://github.com/php/php-src/blob/PHP-7.1/ext/opcache/zend_accelerator_util_funcs.c#L341 The current PR should improve performances by only loading the required symfony services, and not load a very very huge class that is the container. |
…tiated services (nicolas-grekas) This PR was merged into the 2.7 branch. Discussion ---------- [Bridge\ProxyManager] Dont call __destruct() on non-instantiated services | 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 | - While working on making #23678 green, I discovered that if a lazy service implements `__destruct`, then that service is not lazy anymore: it is created at destruct time. That behavior is documented at Ocramius/ProxyManager#258 (+related issues). While I may understand why this behavior is the default for ProxyManager, it does not fit our "lazy-services" use case to me. Typically, nobody wants a database connection to be created to destruct the uninitialized lazy-proxy. Blocks #23678 Commits ------- 2d79ffa [Bridge\ProxyManager] Dont call __destruct() on non-instantiated services
I'm now targeting 3.4, see #23741 |
…ekas) This PR was merged into the 3.4 branch. Discussion ---------- [DI] Generate one file per service factory | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #23601 | License | MIT | Doc PR | - See #23678 for background on this proposal. Commits ------- 4037009 [DI] Generate one file per service factory
The most significant improvement of this PR should be on the CLI, where OPcache doesn't work. |
@@ -671,9 +671,29 @@ protected function dumpContainer(ConfigCache $cache, ContainerBuilder $container | |||
$dumper->setProxyDumper(new ProxyDumper(md5($cache->getPath()))); | |||
} | |||
|
|||
$content = $dumper->dump(array('class' => $class, 'base_class' => $baseClass, 'file' => $cache->getPath(), 'debug' => $this->debug)); | |||
$content = $dumper->dump(array('class' => $class, 'base_class' => $baseClass, 'file' => $cache->getPath(), 'as_files' => true, 'debug' => $this->debug)); |
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 What is the reason to hardcode as_files
value?
After upgrading to Symfony 3.4 our tests won't run on the CI platform because they hit the open files limit.
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.
Because there are many benefits to splitting the container into several files.
ow does this relates to fopen any limit? Service files should be closed by PHP as soon as they are consumed, isn't 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.
@nicolas-grekas I meant the limit imposed by /etc/security/limits.conf
(which includes all loaded files).
You maintain here that you've "updated the Kernel to set that flag in non-debug envs", but this is not the case.
Is there any way to get around this option apart from overriding Kernel:dumpContainer()
(which doesn't seem a good idea)?
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.
/etc/security/limits.conf (which includes all loaded files)
I only see nofile
, which limits currently open files. Doesn't PHP close them once executed, thus the culprit is elsewhere?
get around this option
not really as this should work for everyone...
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.
It does close them, except if the file is autoloaded, and needs to autoload another file, in such a case, the chain will remain open until last statement
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 run the tests with coverage and thus with phpdbg
which may be the curlpit, as you suggested...
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.
Yes, phpdbg has the max_open_file problem , I dont really know why , I guess it loads PHP files differently from other SAPI , I should have a look at the source code for that.
You should increase the limit by using ulimit -n
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 @jpaul
Thank you for this feedback.
Unfortunately ulimits
are not available on Codeship.
This option has been added in `symfony/dependency-injection` v4 and the idea is to reduce the amount of files/classes loaded when the container is initialised, thus reducing memory usage and speeding up things. More info: symfony/symfony#23678
This option has been added in `symfony/dependency-injection` v4 and the idea is to reduce the amount of files/classes loaded when the container is initialised, thus reducing memory usage and speeding up things. More info: symfony/symfony#23678
This option has been added in `symfony/dependency-injection` v4 and the idea is to reduce the amount of files/classes loaded when the container is initialised, thus reducing memory usage and speeding up things. More info: symfony/symfony#23678
This option has been added in `symfony/dependency-injection` v4 and the idea is to reduce the amount of files/classes loaded when the container is initialised, thus reducing memory usage and speeding up things. More info: symfony/symfony#23678
This option has been added in `symfony/dependency-injection` v4 and the idea is to reduce the amount of files/classes loaded when the container is initialised, thus reducing memory usage and speeding up things. More info: symfony/symfony#23678
With this PR, when the new "as_files" option is set on the dumper, it generates an array of files, one per service factory, private or public ones. That's it, nothing else but implementation details.
The benefit should be exactly the same as autoload for class definitions: only the service factories required for the current request are being loaded.
On the technical background, you may wonder if this is relevant with OPcache. If you inspected serious apps with Blackfire, you may have noticed that the container file can take some significant time+memory when the number of services grow. The reason is that even if the class is pre-compiled in shared memory by OPcache, its opcode array still needs to be copied in the memory of the current request. That takes time, and memory.
This PR builds also on static arrays and interned strings to lower the CPU+RAM footprint of the container to the strict minimum.
Yes, doing a require, even an OPcached one, is slower than running an already loaded method. But that number of require/method is low (one per instantiated service, no more). In the end, the dead code elimination provided here wins.
Of course, if anyone can help provide actual numbers, that'd be really awesome.
First bench, front page of the standard edition:
ab -n 100
goes from 1.55s to 1.50sOf course, the page is so thin that measuring the effect of the patch is hard. Yet, all measuring tools go in the same direction, Yay! If the benefit is measurable for a simple app, the benefit for real apps is going to be much higher. Looks like the container can now scale with a growing number of services :)
When the "as_files" flag is not set, the same code as before is generated. I've updated the
Kernel
to set that flag in non-debug envs, so that prod has the optimized code loading strategy, and devs can still open a single file to inspect the factories easily.