Make your Sylius webshop faster with Twig Runtime Extensions 🚀

Make your Sylius webshop faster with Twig Runtime Extensions 🚀

·

3 min read

Intro

First of all, I got my inspiration from this article, which was mentioned on A Week of Symfony #719. As Margaret mentions, Sylius would be a good use case to add these improvements to. So said and done.

Setup

  1. OS: Fedora 32
  2. RAM: 12 gig DDR3
  3. Database: MariaDB 10.4.11 - Docker Container
  4. PHP Server: Symfony CLI - PHP 7.4.11
  5. Default Sylius installation
  6. APP_ENV=prod, APP_DEBUG=0

The changes

For each bundle found in the src\Sylius\Bundle, I searched for Twig extensions. Only extensions that had dependencies I ported to a Runtime Extension. I made all the changes in one commit, so I would be able to easily go back and forth between the original installation and the adjusted code.

So this

ProvinceNamingExtension.php

<?php 

...

class ProvinceNamingExtension extends AbstractExtension
{
    /** @var ProvinceNamingProviderInterface */
    private $provinceNamingProvider;

    public function __construct(ProvinceNamingProviderInterface $provinceNamingProvider)
    {
        $this->provinceNamingProvider = $provinceNamingProvider;
    }

    public function getFilters(): array
    {
        return [
            new TwigFilter('sylius_province_name', [$this->provinceNamingProvider, 'getName']),
            new TwigFilter('sylius_province_abbreviation', [$this->provinceNamingProvider, 'getAbbreviation']),
        ];
    }
}

Became this:

ProvinceNamingExtension.php

<?php 

...

class ProvinceNamingExtension extends AbstractExtension
{
    public function getFilters(): array
    {
        return [
            new TwigFilter('sylius_province_name', [ProvinceNamingRuntimeExtension::class, 'getName']),
            new TwigFilter('sylius_province_abbreviation', [ProvinceNamingRuntimeExtension::class, 'getAbbreviation']),
        ];
    }
}

ProvinceNamingRuntimeExtension.php

<?php

...

class ProvinceNamingRuntimeExtension implements RuntimeExtensionInterface
{
    /** @var ProvinceNamingProviderInterface */
    private $provinceNamingProvider;

    public function __construct(ProvinceNamingProviderInterface $provinceNamingProvider)
    {
        $this->provinceNamingProvider = $provinceNamingProvider;
    }

    public function getName($address)
    {
        return $this->provinceNamingProvider->getName($address);
    }

    public function getAbbreviation($address)
    {
        return $this->provinceNamingProvider->getAbbreviation($address);
    }
}

Profiling

I used Blackfire.io as a profiling tool. I ran the tests on /en_US/taxons/category/dresses. Before each profile, I manually removed the var/cache folder, ran php bin/console cache:clear, and gave the page one refresh.

The default installation profile:

profile toolbar

Detailed view

Some stats:

  1. Avg. Request time: 396 ms
  2. Avg. IO Wait: 8.44 ms
  3. Avg CPU Time: 361 ms
  4. Avg. Peak Memory: 48.8MB

Updated code profile:

Alt Text

Detailed view

Some stats:

  1. Avg. Request time: 362 ms
  2. Avg. IO Wait: 7.52 ms
  3. Avg CPU Time: 355 ms
  4. Avg. Peak Memory: 48.3MB

Comparison:

Luckily for me, Blackfire itself supports making comparisons between two profiles. These are the results:

Detailed view

Alt Text

  1. Diff. Request time: -7ms (1.9%)
  2. Diff. IO Wait: -922µs (11.%)
  3. Diff CPU Time: -6.08 ms (1.68%)
  4. Diff. Peak Memory: -84kb (0.174%)

Conclusion

While the tone in the inspiring article 'suggested' these changes would make a big difference, I find the difference quite small. But It also depends on how much extensions are loaded for that page. Anyhow, each positive change is a good change. So I'd suggest, if you create a Twig extension that has dependencies, just create them as runtime extensions.