-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Significant performance degradation with enable_lazy_ghost_objects: true
#11087
Comments
Has anybody else managed to reproduce this issue? |
Yes, it's drawn back for us to upgrade; this represents a significant performance degradation. |
Starting from the reproduction instructions javar left of:
Run on an AWS EC2 AL2023 c5.large with php version:
Results:
Reproduction code after SF6.4 update:
Reproduction code after ORM v3 upgrade:
Other project:
|
Here is a screenshot of a compare callgraph in Tideways Profiler, it shows the relevant parts that are slow:
The overall slowdown is roughly in line what @rrehbein reported. I will take a deeper look when i have more time. |
I looked at createLazyGhost factory method and found that it acts both as an initializer when invoked the first time, and then as a factory for lazy ghosts. It seems when the initializer code is run every time as well, this causes a lot of wasted CPU and allocated memory. A simple attempt to split both methods up cut 50% of the slowdown: I hardcoded it into my doctrine/orm and symfony/var_exporter components of @javer testproject. So no nice diff, the changed lines are here: https://gist.github.com/beberlei/97899ea7ce4261502a6df1300ab9f181 This would require var-exporter to add these new methods, so I am looking to find a simpler fix that cuts the time and memory in createLazyGhost already. |
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as resolved.
This comment was marked as resolved.
Below is a better patch for the ORM side. Together with this one symfony/symfony#54253, I get the following stats on my machine:
Locally, I also have a native proxy implementation for php-src (I need to write an RFC about it), and with it, I get 3.27s with the current API and implementation. That's good food for thoughts on my side to move this RFC forward :) Here is the Blackfire.io comparison between disabled and patched lazy-ghosts: The patch for ProxyFactory.php--- a/src/Proxy/ProxyFactory.php
+++ b/src/Proxy/ProxyFactory.php
@@ -360,11 +360,11 @@ EOPHP;
*/
private function createLazyInitializer(ClassMetadata $classMetadata, EntityPersister $entityPersister, IdentifierFlattener $identifierFlattener): Closure
{
- return static function (InternalProxy $proxy, InternalProxy $original) use ($entityPersister, $classMetadata, $identifierFlattener): void {
- $identifier = $classMetadata->getIdentifierValues($original);
- $entity = $entityPersister->loadById($identifier, $original);
+ return static function (InternalProxy $proxy) use ($entityPersister, $classMetadata, $identifierFlattener): void {
+ $identifier = $classMetadata->getIdentifierValues($proxy);
+ $original = $entityPersister->loadById($identifier);
- if ($entity === null) {
+ if ($original === null) {
throw EntityNotFoundException::fromClassNameAndIdentifier(
$classMetadata->getName(),
$identifierFlattener->flattenIdentifier($classMetadata, $identifier)
@@ -382,7 +382,7 @@ EOPHP;
continue;
}
- $property->setValue($proxy, $property->getValue($entity));
+ $property->setValue($proxy, $property->getValue($original));
}
};
}
@@ -468,9 +468,7 @@ EOPHP;
$identifierFields = array_intersect_key($class->getReflectionProperties(), $identifiers);
$proxyFactory = Closure::bind(static function (array $identifier) use ($initializer, $skippedProperties, $identifierFields, $className): InternalProxy {
- $proxy = self::createLazyGhost(static function (InternalProxy $object) use ($initializer, &$proxy): void {
- $initializer($object, $proxy);
- }, $skippedProperties);
+ $proxy = self::createLazyGhost($initializer, $skippedProperties);
foreach ($identifierFields as $idField => $reflector) {
if (! isset($identifier[$idField])) {
|
…ects (nicolas-grekas) This PR was merged into the 7.1 branch. Discussion ---------- [VarExporter] Improve performance when creating lazy objects | Q | A | ------------- | --- | Branch? | 7.1 | Bug fix? | no | New feature? | no | Deprecations? | no | Issues | - | License | MIT Creating lazy objects can happen in a tight loop, and then, this has measurable benefits. Found while working on doctrine/orm#11087 Commits ------- bf4cb39 [VarExporter] Improve performance when creating lazy objects
Is the goal to get to the same or better performance than with lazyghosts disabled or is this considered "good enough" result? |
This is good enough to me. The core issue with the current code is the initializer closure that creates a self-reference in the object graph, which adds a lot of pressure on the garbage collector. The other changes are minor perf optim that shouldn't matter much in practice. |
@nicolas-grekas does your change here require the symfony change or are they independent? The improvement is good enough for me too for now. |
The patch are independent. BUT the ORM patch makes the test suite fail on one or two test cases which we need to investigate. |
I don't doubt that the new setup is better, and I'm sure eventually it will catch up, but right now we're seeing the same results as you outline in #11087 (comment), so losing half the performance (from 17s to 29s for setting up a subset of test fixtures). That's many times better than before your patch, of course, but still a fairly high cost... I hope the issue keeps getting some attention until we're in a closer performance range. EDIT: turns out I was comparing on 2.19.2 which lacks the patch; using 2.19.3 seems to fix the issue entirely for our use-case (at least with synthetic loads, we'll have to compare for real load). |
Good news! We can close this issue then :) |
Let's! Thanks @nicolas-grekas ! |
I can confirm that after upgrading to doctrine/orm 2.19.3 turning on Thank you very much @beberlei and @nicolas-grekas ! |
Bug Report
Summary
After updating ORM from 2.15.2 to 2.17.1 and turning on
enable_lazy_ghost_objects
as it's suggested I've noticed a significant performance degradation during test suite running. The overall suite became slower by noticeable 30%. Localizing the slow spot I've found that ProxyReferenceRepository::load() now takes 50 seconds in total instead of the previous 3.86 seconds, i.e. became 13x slower.Tracking down
ProxyReferenceRepository::load()
->ProxyReferenceRepository::unserialize()
I've found thatEntityManager::getReference()
became 23x slower (2.055 seconds -> 47.888 seconds in total). This real project has hundreds of entities and complex relations between them. During running test suite with 1k+ scenarios a lot of fixtures are generated, saved and loaded (including references):I've created a test command to reproduce the issue, and running it in the same project I get:
I've created a small reproducer: https://github.com/javer/doctrine-orm-lazy-ghost
It's a fresh Symfony 6.3 project with one additional commit: javer/doctrine-orm-lazy-ghost@36a3e20
Running
bin/console app:test:orm
I have the following results:Slowdown numbers are not so high as in a real project, but there is only one simple entity without any relations, and no save/load operations, only getting references. Even in this simple case
ProxyFactory::getProxy()
slowdown by almost 4x is too expensive for usingenable_lazy_ghost_objects
.Taking into account that
enable_lazy_ghost_objects
will be alwaystrue
in ORM 3.0 I think it's worth fixing it.How to reproduce
git clone https://github.com/javer/doctrine-orm-lazy-ghost cd doctrine-orm-lazy-ghost composer install bin/console app:test:orm
Expected behavior
Turning on
enable_lazy_ghost_objects
shouldn't have a noticeable impact on the performance.The text was updated successfully, but these errors were encountered: