Magento's 500ms Object Creation Problem
45sReveals a shocking performance bottleneck that affects every Magento store, sparking curiosity and concern among developers.
▶ Play Clip"Delivers on the promise with a concrete prototype and performance data, though some parts are technical and may not appeal to all."
This video explores how PHP 8.4's native lazy objects can dramatically improve Magento 2's bootstrapping performance. The presenter, Benjamin, demonstrates a prototype that makes every class lazy by default, reducing object creation and autoloading overhead, potentially saving hundreds of milliseconds per request.
Magento 2's object manager is slow, spending 500-2000 milliseconds per request on object creation and graph building, leaving room for improvement compared to Symfony's DI container.
Benjamin met Ivan, a Magento performance expert, who expanded on the prototype and posted about 200ms improvements, seeking sponsorship for an open-source library. Mark Shust also discussed native lazy objects on LinkedIn.
Magento's proxy classes use code generation but are not used effectively by default, so they don't provide real performance benefits. Most generated proxies are actually used, negating lazy loading.
Benjamin's prototype makes every class lazy by default using PHP 8.4's native lazy objects, with no overhead. It modifies the compiled object factory to create proxies via reflection and an initializer callback.
Without lazy loading, do create is called 74 times with deep recursion (up to 14 levels), taking 99ms. With native lazy objects, do create is called 211 times but non-recursively, and total time is much lower.
The main benefit is reduced composer autoloader calls, saving about 40ms. Lazy objects avoid instantiating and autoloading unused classes, which Magento's proxy mechanism fails to do.
Benjamin hopes the Magento community integrates these learnings into core or a third-party library, and that Ivan finds sponsorship to make the feature production-ready.
Native lazy objects in PHP 8.4 can significantly speed up Magento's object manager by avoiding unnecessary object creation and autoloading, potentially saving hundreds of milliseconds per request. The community is encouraged to adopt this approach.
What is the typical time spent on object creation in Magento 2 per request?
500 to 2000 milliseconds
00:02
How does Magento's proxy mechanism work?
It generates proxy classes that inject the object manager and delegate method calls to a lazily created instance.
02:31
What is the key benefit of native lazy objects in PHP 8.4?
They allow making every class lazy by default with no overhead, reducing object creation and autoloading.
04:40
How much time was saved by avoiding autoloading in the prototype?
About 40 milliseconds.
10:44
What is the main reason native lazy objects are faster than Magento's proxies?
They avoid instantiating and autoloading unused classes, while proxies still trigger autoloading for all dependencies.
11:15
Object Manager Performance Issue
Quantifies the problem: 500-2000ms per request on object creation.
00:02Prototype: Lazy by Default
Shows a practical implementation using PHP 8.4 native lazy objects.
04:40Autoloader Savings
Identifies the primary performance gain: reduced autoloader calls.
10:44Call for Community Action
Encourages integration of the approach into Magento core or libraries.
12:20[00:02] Magenta 2 serves as a great example on how native lazy objects in PHP 8.4 could massively improve bootstrapping performance in every single request. The performance in every single request. The object manager in Magento 2 is quite a
[00:15] object manager in Magento 2 is quite a beast and we frequently see 500 to 200 beast and we frequently see 500 to 200 milliseconds spent in every request just creating objects and the object graph. Compared to the Symfony dependency
[00:28] injection container, it leaves a lot of room for improvements on the table. In this video, I'm going to walk you through my journey with the Magento through my journey with the Magento object manager and uh a prototype for
[00:41] native lazy objects that I've been working on and that Magento could benefit hugely from in their bootstrapping performance. Mo, I'm Benjamin and my work is focused on PHP performance topics for the last 10
[00:56] years, helping thousands of developers, companies, and open-source projects along the way. A few weeks ago, I met up with Ivan in Amsterdam. Ivan is my favorite Magento performance expert, and I really like bouncing ideas off of him.
[01:10] For example, I showed him my prototype for native lazy objects in Magento. He expanded on the work and posted about it on LinkedIn last week, showing a 200 milliseconds performance improvements in this case and asking for merchants,
[01:23] agencies, and others to come forward to sponsor with these changes to the object manager. He's expecting a 200 milliseconds performance improvement for every request. and he's asking merchants, agencies, and others to come
[01:37] forward to sponsor this so he can release it as an open-source library that everybody can benefit from. During our meet, he mentioned it would be hard to get this kind of change into the Magento core and replacing this code
[01:50] with a third party module is difficult because the object manager is deeply entangled in everything that Magento does. I am a hopeful and positive person about these things and I want to see every Magento 2 store owner benefit from
[02:04] this. Mark Shust from the Magento Academy also recently posted to LinkedIn on native lazy object in PHP and how they could benefit Magento. He contrasts they could benefit Magento. He contrasts Magento's proxy functionality with PHP
[02:17] 8.4's native lazy features and just generally shows how it works. The discussion in this post however went into how Magento could generally improve from using native lazy objects and this is something I'm going to talk in this
[02:31] video about. While Mark mentions Magento has proxy classes which work using code generation like for example in Symphony this concept is not used effectively by Magento in the default setup and you
[02:44] cannot see real performance benefits from it. When a service definition in from it. When a service definition in Magento declares an argument dependency Magento declares an argument dependency to service using the magic proxy suffix
[02:56] then Magento knows to generate a proxy class for that during the DI compile component that you should use for production use. If we look at the generated proxy class, we see that instead of all the dependencies
[03:10] injected, it injects the object manager and then implements a pattern where on all the public methods, it calls get subject and delegates to the original method. Get subject then uses the object manager to create the instance and
[03:25] lazily loads it at that point. we can look at a call graph trace in tight base look at a call graph trace in tight base to see how effective this um lazy proxy generation mechanism in Magento is. So create the trace and then remembering
[03:40] create the trace and then remembering from uh our look at the code that we can search the call graph for search the call graph for first the use of a proxy class
[03:55] first the use of a proxy class with a constructor. We see how often proxies are created in this case. So uh one page of calls of proxies are being generated.
[04:08] being generated. Um a handful of proxies being used and then we can look for get subject how often that is called. So we can see again a lot of times get subject is called. So um it's it's not really an
[04:25] effective use of proxies. most of the proxies that are generated in Magento or most of the classes that are generated as proxy are actually being used. So um there's not really an effective lazy loading mechanism in place here. Let's
[04:40] look at my working prototype for native lazy objects in Magento. It works different than Magento's proxy objects by making every class lazy by default. The benefit of PHP 8.4 for here is that there's really no overhead and using
[04:56] proxies and we can just implement it this way natively. Looking at the this way natively. Looking at the compiled um object factory, we can see compiled um object factory, we can see how I added this code here. So first we
[05:10] how I added this code here. So first we have a way of running this functionality without um using native lazy objects. So we um using native lazy objects. So we delegate to the function do create. The
[05:22] function do create is the original code that Magento had before my changes. So I that Magento had before my changes. So I renamed the public function create to private function do create and then I added my own prototype code on top here.
[05:38] So we generate one trace uh that immediately calls the do create method and then let's look at the prototype. First we resolve any requested type because it could be a virtual class to
[05:53] its final class name. We check for PHP 8.4. Then we check that the class is not already a proxy uh based on the magenta proxy mechanism. We keep that. um we
[06:06] check that the class exists. We have to check that it's not a subclass of the session handler because internal classes cannot be proxied. This is a special case and I assume for a more complete solution more classes need to be added
[06:21] there. Um this is just the way to get it working with the Magento demo store that doesn't have any plugins or uses any additional PHP functionality. additional PHP functionality. then we also need to restrict it to um
[06:35] services that uh have no additional arguments here. So um that without this change um it's not working um more uh I would need to investigate a lot more to
[06:47] find out what's the problem to improve the solution here. Then we see how uh a native uh pro proxy is created using reflection and the initializer callback
[07:00] reflection and the initializer callback just uh uses this proxy and it calls the just uh uses this proxy and it calls the do create method like um uh in a case where the class is not proxied. So this is a very simple way of making
[07:14] essentially every class a lazy proxy um if it fulfills the requirements above if it fulfills the requirements above here and um that that is quite a lot of uh classes that um we can see now in a core graph.
[07:29] First let's look at the example without native um lazy loading. So in the call graph we look for the compiled class and how it um is getting called. So the do
[07:45] create method is the call um is the function that initializes the services and uh injects all the dependencies recursively. recursively. And you can see here because uh Magento
[07:59] And you can see here because uh Magento has very little lazy loading and in has very little lazy loading and in scale there's a huge recursion happening by instantiating services. So in tideways you can see that by the
[08:11] recursion counter functions on the top level we have 7 uh 74 calls to the do create method. So this is the object manager creating objects
[08:24] and then recursively we can see how it goes one level, two level, three level, goes one level, two level, three level, four levels deep and um do create
[08:36] uh by filtering for this we can see here cases where it goes 14 14 level deep um to create the dependencies for a service. So
[08:48] huge object graphs that are being created by Magento. And the time for this is 9 99 milliseconds in total just for creating milliseconds in total just for creating all the objects and um recursively the
[09:01] dependencies. And um for a call graph that has my And um for a call graph that has my native lazy object uh improvement, we can see that there's quite a different result. we see that do create is called
[09:15] result. we see that do create is called a lot more 1, uh uh 211 times. However, that that is not a regression. It's not worth the way native lazy objects work
[09:28] is that we don't do this recursively now. Instead, only when the objects are being used, they are created. And this is not really a recursive relationship anymore. So we see how the recursive object creation moves to the top in the
[09:43] call graph. Um it's being called way more often. Um however time wise it's not um it's much faster. The reason for this is that we need to actually compare
[09:58] all the numbers uh being called here in total to the do create calls um in the native lazy example. So here we can see that only the top levels here already
[10:12] that only the top levels here already have 1,200 calls and then we see uh there are a few hundred more calls to do create um as the recursion increases. However, for the case where we use native lazy objects in total there is um
[10:29] native lazy objects in total there is um um just fewer calls to creating objects and um that makes um quite the difference already. So how can we um assess the impact of this?
[10:44] So we compare those two core graphs. We can already see here there's a massive performance improvement uh for this about 100 milliseconds. However, I wouldn't account that completely towards the native um change but uh also to um
[11:02] to other things that are happening here. So let's look at the core graph. So let's look at the core graph. And we see that the most important benefit that we have here is that the composer autoloader is not called a lot
[11:15] anymore. And that is the actual benefit that lazy objects bring. If you don't instantiate the objects that you don't need, then the autoloader is not triggered. Um in case of the Magento lazy objects,
[11:31] this is not working because every proxy also needs to instantiate um all the dependencies and the proxy objects. It's triggering the autoloader much much uh more often and we skipping that um in the native lazy example. And
[11:49] we can see here that we have about 40 milliseconds saved just by not autoloading so many classes anymore. And that is the benefit of uh using native lazy objects in a DI container by default and not um using a mechanism
[12:06] where you opt into that. I hope I convinced you that native lazy objects are going to revolutionize dependency injection in PHP and Magento is a great example for that because the current object manager is so slow and magenta
[12:20] object graphs become so massive because of the extreme number of dependencies that servers usually have all of which which will be instantiated and autoloaded and initialized on every request. Let's hope the magenta
[12:34] community finds a way to integrate these learnings into the framework or a third party library so that every magenta store owner can benefit. I hope that uh Ian finds somebody to uh work on this feature and make it production ready.
[12:48] This concludes my series on native lazy objects that started with part one of the series looking at doctrine and then also looking at symfony proxies. If you want to continue your journey with PHP performance topics, subscribe
[13:04] to the channel or the newsletter. The link is in the description.
⚡ Saved you 0h 13m reading this? Transcribe any YouTube video for free — no signup needed.