orm: Significant performance degradation with `enable_lazy_ghost_objects: true`
Bug Report
Q | A |
---|---|
BC Break | no |
Version | 2.17.1 |
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 that EntityManager::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):
Time in seconds | enable_lazy_ghost_objects: false | enable_lazy_ghost_objects: true | Slowdown |
---|---|---|---|
EntityManager::getReference() | 2.055900 | 47.887696 | 23.29x |
UnitOfWork::tryGetById() | 0.277827 | 9.693281 | 34.89x |
ProxyFactory::getProxy() | 0.751345 | 27.942798 | 37.19x |
UnitOfWork::registerManaged() | 0.611383 | 9.814827 | 16.05x |
I’ve created a test command to reproduce the issue, and running it in the same project I get:
Time in seconds | enable_lazy_ghost_objects: false | enable_lazy_ghost_objects: true | Slowdown |
---|---|---|---|
Total | 1.922614 | 8.043023 | 4.18x |
EntityManager::getReference() | 1.784766 | 7.948818 | 4.45x |
UnitOfWork::tryGetById() | 0.232570 | 0.254660 | 1.09x |
ProxyFactory::getProxy() | 0.605608 | 4.091646 | 6.76x |
UnitOfWork::registerManaged() | 0.547479 | 3.200220 | 5.85x |
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: https://github.com/javer/doctrine-orm-lazy-ghost/commit/36a3e20b93fe5dba1a520a24351ad794477185e9
Running bin/console app:test:orm
I have the following results:
Time in seconds | enable_lazy_ghost_objects: false | enable_lazy_ghost_objects: true | Slowdown |
---|---|---|---|
Total | 2.388989 | 4.273117 | 1.79x |
UnitOfWork::tryGetById() | 0.340158 | 0.349181 | 1.03x |
ProxyFactory::getProxy() | 0.709970 | 2.551538 | 3.59x |
UnitOfWork::registerManaged() | 0.776654 | 0.818202 | 1.05x |
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 using enable_lazy_ghost_objects
.
Taking into account that enable_lazy_ghost_objects
will be always true
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.
About this issue
- Original URL
- State: closed
- Created 7 months ago
- Reactions: 11
- Comments: 19 (16 by maintainers)
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.
Below is a better patch for the ORM side. Together with this one https://github.com/symfony/symfony/pull/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: https://blackfire.io/profiles/compare/4561cec4-97af-4c40-a180-ee5745ca8119/graph
The patch for ProxyFactory.php
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 https://github.com/doctrine/orm/issues/11087#issuecomment-1994002826, 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).
I can confirm that after upgrading to doctrine/orm 2.19.3 turning on
enable_lazy_ghost_objects
makes the initial test suite slower only by negligible 5%.Thank you very much @beberlei and @nicolas-grekas !
For what it’s worth, we also see no performance difference at all after deploying to our production:
So I can only agree with the decision to close it 👍