-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Cache] Optimize ArrayAdapter by using a generator #17435
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
Not sure if this is worth it, the values are already loader into memory by the doFetch method so this doesn't save any memory. I'm not an expert but this could reduce performances by replacing arrays with generators that are a more complex structure. |
For ArrayAdaptor is definitely must thing. Because if I have like million keys array, and I am asking for half of it, I will be then having 1,5 the same array in memory. Afterwards, if I just need two values from it will be not efficient. |
The values won't be duplicated because of copy on write. |
https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php#L67 adds value from |
This is wrong: it is subject to race condition if you change the cache inside a loop on the retrieved items (as your generator does not actually read the cache when asking to read it but when iterating) |
AbstractAdapter yes would need changes to make fetching on iteration, but on ArrayAdapter it's visible improvement in performance With generators:
With arrays
|
@GCDS fetching on iteration is precisely what is broken. |
I started a similar patch locally :) My own bench show also that generator are faster for bigger array, and a bit slower for short array, but the added flexibility of generator is really worth it. |
@nicolas-grekas so bad that STANDARD does not allow it :( |
Does not allow what? PSR-6 allows returning a Traversable, so yes, generators are allowed to me. |
@GCDS the race condition issue is related to the implementation. Changing the standard would not change the fact that delaying the cache fetching means that the cache can change in the meantime. This is not related to the standard at all, but to common sense. |
@stof I am talking only about ArrayAdaptor now as others need modification for race condition issue |
@GCDS the issue in your code is that you delay the validation of keys. AFAIK, this is not a hard requirement for a generator-based implementation. It is just an issue in your naive implementation of generator-based cache. And delaying the validation later than the getItems methods simply means it is impossible to catch it. So a bad standard would be a standard allowing it IMO. |
@stof Yes, I understand the problem here... I trying different aproaches to keep validation happening before generator somehow but I am not able to think a way of doing it... |
Removed unnecessary code and managed to get verification of keys before return generator. Updating cache/integration-tests pull request also |
@@ -177,4 +172,16 @@ private function validateKey($key) | |||
|
|||
return $key; | |||
} | |||
|
|||
private function createGenerator(array $keys) |
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'm not sure splitting this to a private provides any benefit, do you?
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.
By doing this, I am getting key validation before returning a generator. Otherwise, you will not get invalid key exception until you call the first iteration
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 this is about making some logic running before the creation of the generator. If you inline the method, all the code is part of the generator and so runs only when you start iterating. Here, the code before the private method is not part of the generator logic.
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.
Oh ok, of course!
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's possible to do inline function but I thought from code style approach it's not a good way
/**
* {@inheritdoc}
*/
public function getItems(array $keys = array())
{
foreach ($keys as $key) {
$this->validateKey($key);
}
return (function (array $keys) {
$f = $this->createCacheItem;
$now = time();
foreach ($keys as $key) {
$isHit = isset($this->expiries[$key]) && ($this->expiries[$key] >= $now || !$this->deleteItem($key));
yield $key => $f($key, $isHit ? $this->values[$key] : null, $isHit);
}
})($keys);
}
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 prefer the private method :)
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 is still not inlining the function. It still uses a separate function, even though it is anonymous rather than named.
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.
Sorry for incorrect naming
See #17438 for a complementary PR on AbstractAdapter |
private function createGenerator(array $keys) | ||
{ | ||
$f = $this->createCacheItem; | ||
$now = time(); |
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.
you need to call time() in the loop now since you don't know how many time will be spent between iterations
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
For some reason travis cached did not installed new version of cache/integration-tests :( |
Fixed the composer problems so code is now passing tests. Good to merge |
Status: Reviewed |
Any changes on this? |
@GCDS: @nicolas-grekas wrote in #17438 that he's off for a couple of days. I guess, we'll have to wait until he returns. 😃 |
I think we can close here because #17438 has been merged, right? Or do I miss anything? |
wow, why closed? ArrayAdapter was not fixed in #17438 |
@xabbuh No, because #17438 introduced Generators to all classes but ArrayAdapter. @nicolas-grekas designed #17438 as a complementary PR to this one, see his comment. Please reopen this PR. |
I'm taking care of this now |
This PR was merged into the 3.1-dev branch. Discussion ---------- [Cache] Use generator in ArrayAdapter | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #17435 | License | MIT | Doc PR | - Commits ------- 367e784 [Cache] Use generator in ArrayAdapter
Added to generators to where possible to optimize code and save memory especially on
ArrayAdapter
.Code works and test pass with fixed cache/integration-tests (Currently does not support
\Traversable
)