Author: Thomas Brankaer
During my research on Blackfire (https://blackfire.io) and its functionality, I decided to focus on the category pages of Magento 1. Category pages are requested the most in the whole framework, so each performance improvement results in a better user experience when navigating through the products catalog.
After I created the first profile on my local project environment, which is using only configurable products, I received the following results.
We see that our profile has a wall clock time of 15.8 seconds, which is disappointingly slow. Wall clock time is a calculation of your CPU (amount of time used for processing instructions) + the I/O time (network + disk time). With these results, our page asks for further investigation.
When following the red path - sequence of red blocks that were the most time consuming- I saw that 59,12% of the whole request wall clock time goes to the getConfigurableAttributes function.
We now see that our getConfigurableAttributes function has 3 callees, functions that are called from the current function. One of these functions is the Varien_Data_Collection_Db::load function, which is using 98% of all callees time. A question we could ask ourselves: “why is Magento loading 21 collections and is this necessary?”
Above a screenshot of all functions and subfunctions called from the getConfigurableAttributes function, with a clear red path to follow. We see that loading a collection class triggers the afterload function each time, which is calling again other functions and so on... At the end of our path we see that the attributesCompare function is using 39.65% of all time and is called 31122 times.
Let’s take a deeper look at each important function on the path so we have a better understanding of what’s happening and why attributesCompare is called that much. We start with the getAttributeById function in the ‘Mage_Catalog_Model_Product_Type_Abstract’ class.
The only thing that this function does, is calling the getSetAttributes to receive all attributes for a specific product. It loops all attributes until the attribute is found that matches the attributeId parameter. If found, it returns the attribute. We know that the getSetAttributes function is the next important function on our path, so we continue our research by taking a look there.
This function will return all products of a specific product (like mentioned before) with the loadAllAttributes function, but also calls the getSortedAttributes function. Which is the main problem as we can conclude from our Blackfire profile (47.23%).
This function checks which attributes are in the provided attribute set and sorts them with the uasort function which calls the attributesCompare on its turn. Like said before, this function is the end function on our path and must be improved. We know that this function repeated for each product, what is useless because a specific attribute set will always keep the same attribute order for each product that is using that set. Let’s try to rewrite this functionality.
Unfortunately this function is located in an abstract Magento class, so a proper rewrite is not possible. We need to override the whole class instead. We do this by placing the ‘Mage_Eav_Model_Entity_Abstract’ class in the ‘app/code/local’ pool on the same file path as the file located in the ‘app/code/core’ code pool.
First of all we add a static variable. This variable will be used the first time the attributes are sorted and keeps the results on a specific attributeset id index. This way, we don’t need to calculate these attribute positions anymore when the same attributeset id is provided. We just return the data set on our variable.
We then modify the getSortedAttributes function and make the following changes.
We check if our key exists. If so we return the data. If not, we calculate it and set the array index at the end for further use.
Let’s profile our page again and see if the changes took affect.
We see an improvement of 45% wall clock time on our whole page! Our CPU time is decreased with 48%.
Above a comparison of the two profiles. The previous and the current one. The blue blocks indicate improvements with the wall clock time in seconds. A simple change with huge effects. Of course it’s also possible to view the profile not in comparison mode.
Further improvements could be to run the collection load once for all products instead of 21 times separately. Handling the problem at the top of our red path, is mostly a better idea because each sub function follows. Because in this case each product could have different configurable attributes configured, this is maybe not the best idea. Be aware this is not the standard list.phtml Magento template. In contrast to this template, the getSortedAttributes function is core Magento functionality and could be a quick win for different Magento projects.
This issue was mainly a CPU problem, but you can also have other factors that could cause problems. It’s a good idea to inspect all tabs provided. Memory, Network, Http, Queries, etc.. A good example are import /export scripts. These could easily cause CPU problems, but also memory problems. Let me give you an example of an other case. This time about a Magento’s varien core file.
When importing product images with one of Magento’s cronjobs, an ‘out of memory’ error was thrown. I decided to use Blackfire’s CLI this time, because cronjobs could be time consuming. Especially when handling import scripts. This profile was generated with the free version of Blackfire, so some tabs could have disappeared.
Above the result of our profile. Be aware that we are viewing our data under the memory tab this time, because our error is memory specific. Our path is now lightblue instead of red.
The most time consuming function is the imagecreatefromjpeg that is only called 32 times. These 32 stands for the number of images that where imported before the memory error was thrown. We can also conclude that for each image a new instance of the ‘Varien_Image_Adapter_Gd2’ is created. The constructor of the varien_image calls the open function of this class, which calls the imagecreatefromjpeg at his turn.
All these images are created and kept in memory, but nowhere removed from the memory.
So it’s obvious that these will thrown an error after a while. Especially when importing a lot of images. Let’s see how we can change the script so that our memory is released again after each image import.
If we take a look at the open function of our ‘Varien_Image_Adapter_Gd2’ class we see that the current image is saved in a variable $_imageHandler.
It could be a good idea to destroy the image set in this variable after the class is not used anymore. At that time, the image is already imported so we don’t need this anymore.
Let’s override the ‘Varien_Image_Adapter_Gd2’ class from our Magento 1 lib directory by adding the same class under the ‘app/code/local/Varien/Image/Adapter/’ path.
A good place to write our functionality could be in the destructor. This function will always be called during the shutdown sequence of a particular object. We add the following code:
Here we trigger the @imagedestroy function on our variable. By doing this, the image object will be destroyed and the memory released. Let’s profile our script again and see the impact.
We now see that our memory is decreased with 209MB! Also each product was successfully imported now (200+ images) and the memory kept stable which is the most important. We are sure now that we can run larger imports also.
I hope these two examples demonstrate you the power of Blackfire when developing new code or maintain existing.
Blackfire Silver Partner
PHPro is Blackfire.io Silver Partner. Blackfire empowers all developers and IT/Ops to continuously verify and improve their application performance, throughout its lifecycle, by getting the right information at the right moment. If you need extra help, performance consultancy to improve your PHP applications, don't hesitate to contact the PHPro PHP Performance Team.