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
Restore a definition cache for autowiring and Container::make() performances #564
Conversation
…rmances This restores some kind of cache like there was in PHP-DI 4 and 5 (which was removed during v6's development). However this is not a full restore, this new cache is very simple and targeted at specific use cases. Related to #543 and #547. Compiling the container is the most efficient solution, but it has some limits. The following cases are not optimized: - autowired (or annotated) classes that are not declared in the configuration - wildcard definitions - usage of `Container::make()` or `Container::injectOn()` (because those are not using the compiled code) If you make heavy use of those features, and if it slows down your application you can enable the caching system. The cache will ensure annotations or the reflection is not read again on every request. The cache relies on APCu directly because it is the only cache system that makes sense (very fast to write and read). Other caches are not good options, this is why PHP-DI does not use PSR-6 or PSR-16. To enable the cache: ```php $containerBuilder = new \DI\ContainerBuilder(); if (/* is production */) { $containerBuilder->enableDefinitionCache(); } ``` To be clear: compilation should be used first. This cache is only for specific use cases.
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 noticed that the new cache implementation is different from the one in v5: in v5 each definition is cached and fetched separately, while here it's all stored under a single cache key. Furthermore, every time a new definition is added to the cache, the whole cache content (which might be large) is sent back to APCu. I don't have enough knowledge to tell how well APCu handles this usage pattern. Any insights on that?
- If caching is enabled together with compiled container, APCu cache is applied for all object definitions, even those that have been compiled. It actually sounds like there is no point in using both caching and compilation for the same container. Is that correct?
// Object definitions are used with `make()` | ||
|| ($definition instanceof ObjectDefinition) | ||
// Autowired definitions cannot be all compiled and are used with `make()` | ||
|| ($definition instanceof AutowireDefinition); |
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.
this seems redundant, as AutowireDefinition
is an extension of ObjectDefinition
(so already covered in like 86 above)
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 indeed, I wanted to make it extra clear (and to leave a comment that can be well understood), even though the second instruction will never be executed.
Thanks for the review @amakhrov. You are right that this cache is pretty different, I forgot to mention I am reusing what I did in #491 (it was supposed to be an optimization of the cache before I scratched it completely after working on compilation). The thing is I decided to reuse #491 when I first started to work on that and planned to cache everything. In the meantime, I realized I can skip caching most definitions except objects/autowiring. So in the end, there will be much less entries in the cache (especially with compilation. So I'm not sure storing everything in one entry is the best strategy anymore.
Same here, so you might be right in that it's safer to keep v5's behavior. Yes it will work as you describe, but:
So it sometimes make sense to enable both features. |
@mnapoli I tested performance in multiple scenarios. Results:
Test methodology:
Highlights:
|
This is great news, that means that with the PR there should be at worst no performance regression in v6 (and at best of course the compiled container should speed things up).
I've opened #565 to keep track of this. It seems it might be easy to implement, however I don't want to delay 6.0 any longer than necessary so this kind of improvements could come in a 6.1. |
@mnapoli I'll check that later today. As for the caching performance in this PR: It just stroke me that I only tested a single request scenario. |
You could run apache bench with concurrency ( |
Since the container is compiled we will store much less entries (definitions) in the cache. So it makes sense to separate each entry because there should not be a lot of entries. That will prevent cache stamped.
Each definition is now stored in a separate cache entry. |
|
||
public function getDefinition(string $name) | ||
{ | ||
$definition = apcu_fetch(self::CACHE_KEY . $name); |
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 using apcu_entry
could reduce the risk of cache stampede. What do you think?
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.
apcu_fetch
because I was storing all definitions in the same key but now that all are stored in separate entries it would make more sense. I'll change that.
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.
Just remembered why we can't use apcu_entry
-> some entries cannot be cached (objects, etc.)
Given that the performance regression is resolved with this I am going to merge it and continue improving the performances of the compiled container in #566. Thank you! |
This PR restores some kind of cache like there was in PHP-DI 4 and 5 (which was removed during v6's development). However this is not a full restore, this new cache is very simple and targeted at specific use cases.
Fixes #543 (indirectly) and related to #547 (workaround).
Compiling the container is the most efficient solution, but it has some limits. The following cases are not optimized:
Container::make()
orContainer::injectOn()
(because those are not using the compiled code)If you make heavy use of those features, and if it slows down your application you can enable the caching system. The cache will ensure annotations or the reflection is not read again on every request.
The cache relies on APCu directly because it is the only cache system that makes sense (very fast to write and read). Other caches are not good options, this is why PHP-DI does not use PSR-6 or PSR-16.
To enable the cache:
To be clear: compilation should be used first. This cache is only for specific use cases.
TODO:
get()