Hello,

today we want to talk a little bit about how Magento 1 Shops can even today in 2022 be improved. As of October 2022, there are over 167000 active eCommerce sites running Magento 1 and nearly 100,000 running Magento 2, representing approximately 0.9% of all online shops. This is just one of the Magento usage statistics that cement it as a major player in the eCommerce industry. Well, now we have 167000 pages that are certainly adapted, modified, and maybe can be improved. Not every seller has the budget to upgrade to a more complex Magento 2 shop, taking into account that many companies are moving forward from Magento 2 to more modern systems like Sylius. 

But lets keep these things aside for now. As initially said, we want to talk a little bit about performance of code today. Maybe you're a developer that works on a Legacy Platform with Magento 1 and you have to improve a couple of things. Maybe the site is to slow, maybe a custom feature takes ages to render, even with Varnish and Redis in place. Now, what can you do about it? The answer is: it depends! We've analyzed some research results of Magento for you and will talk about the Top 5 performance issues that we also have experienced in the wild today.

  1. Calculating the size of an array on each iteration of a loop
  2. SQL queries inside a loop
  3. Loading the same model multiple times
  4. Redundant data set utilization
  5. Inefficient memory utilization

That sounds like something we can use to check the impact of our own code, our own Magento 1 shop. Lets have a look at real life code examples and then how we could potentially optimize them. Today, we want to speak only about the first two items of that list.

Loop counting issues


The advantage of a PHP loop is that the programmed code in the PHP loop can be executed several times. Without it, you would have to copy the code several times or work with goto statements to jump from the end back to the beginning. Loops are thus a very elegant way to work and are an indispensable part of today's programming languages. The loop can theoretically be executed endlessly. This is done in many other languages for graphical interfaces, for example.

Now, what does us tell this about our own code? Maybe we check a remainder of an array when we work on an array and unset things in the middle of the logic. A blocking loop would look like so:

php
for($i = 0; $i < count($data); $i++) {
... 
}

In this example we have even more things to optimize. Especially for long-running loops - well, even any loop - you should never increment after evaluation. Its in 99% not required. However, the performance impact on this is absolute minimal, if noticeable. Its more of an evaluation and CPU usage, which will in the long run remain lower, compared to the `$i++` alternative. 

Lets talk again about the problems of this loop. The performance difference between an foreach loop with an traversable array and a for loop that counts is in the microseconds range, but there its really huge. Why foreach takes about 2 microseconds, for and declaration if `$i` takes 12 microseconds. This is a gigantic performance difference. Now, we have to think in dimensions where a code is not only called once per quarter, but up to a million times per day or even hour. If we save the difference each time, the outcome will be faster rendering of the requests, thus more customers that can be served eventually. Its not always worth to invest the time to refactor something, but on many places it simply is - not just because its so super easy to rework a for loop. Since we can assume that `$data` is an array (as it implements the Countable Interface), we can just as well traverse over it - at least in Magento its absolutely safe to assume that everything that is Countable is also Traversable.

Our much better loop would look like this:

php
...
foreach($data as $d) {
    ...
}
...

The performance difference is insane. It does the same and depending if you need the evaluated `$i` in the first place you can also call `array_values` on the array and then just read it from the loop directly. There is no difference if a pre-defined variable tells you the loop index or if you pick the value from the array directly. Lets have a look about hard numbers for testing purposes:

shell
count in evaluation, increment after evaluation loop took 21.9196870327 seconds
foreach loop 13.270086050034 seconds

That is nearly 40% faster. Here you can see how easy it is to waste time and how even easier it is to fix that. The good news is that this applies not just to Magento 1, also to Magento 2 or really any other PHP script that you're working on.


SQL queries inside a loop


This is actually our favorite. Its fairly easy to adapt, there is a noticeable performance change and customers are always excited to see themselves how their feature is now super-fast.

Lets have a look about the culprit:

php
foreach ($this->getProductIds() as $productId) {
    $product = Mage::getModel('catalog/product')->load($productId);
    $this->processProduct($product);
}

This is a terrible loop. It will cause Magento to send SQL queries to the database for each request, which costs you not just the SQL overhead but also the SQL execution time. The whole query could be done once, can even be optimized to its core and be run faster so much quicker. 

As we can already imagine,  there is quite some room for improvement. The difference is just measurable by a lot, starting at 10 products you want to process. Whilst the overhead of a collection based call is around less than 1s (even for huge collections), the operation time scales up when using the aboves example. However, it can take a second or two to actually initialize and work on a bigger collection.

A much better approach is this:

php
$collection = Mage::getModel('catalog/product')->getCollection()
    ->addFieldToFilter(‘entity_id’, [$this->getProductIds()])
    ->addAttributeToSelect(['name']);

foreach ($collection as $product) {
    $this->processProduct($product);
}

One major part here is that we specify the attribute we actually want to read from the database. If we want everything, we can also use `'*'` as field declaration, but it will be at the cost of performance. And you really don't need all the attributes every time. Actually, its much more likely that you will never really need all attributes. Lazy developers use it because they don't want to add an array with the required attributes. 

As you can see, its very easy to fix the 2 major speed killers in Magento 1 stores. If your customer is complaining about slow loading times, the chances are high that this is one of the reasons for it. Many times, its more like a huge motor that not just needs some oil but also new valves, so its hard to determine completely what slows a shop down without additional tools like NewRelic or Blackfire. 

If you need help to improve your Magento 1 performance, you can always reach out to us, maybe we can help you a little bit with your existing shop, or train your developers to identify these issues better. Have a great day, looking forward hearing from you.