Mastering Symfony2 Performance – Internals

Cheetah

Symfony2 is said to be slow. Nothing can be further from the truth! Our Symfony2 instances handle more than 600 req/s and are working great. To have such results, everything needs some performance optimizations.

Following a great feedback after our previous post about Doctrine’s performance (thank you! :-)), we decided to give you some more advices of reaching the top from your Symfony2 projects.

We’re going to make separate article which will show you what to not afraid of in Symfony2 ecosystem (annotations, Twig etc.) so stay tuned! 🙂

1. Use Stopwatch Component to profile your code

Thanks to the Stopwatch Component, you’re able to see times needed to run parts of your code and amount of memory consumed by it.

The common use case is to track the time needed to perform connections to the third party webservices.

The resulting graph of a code-flow will be showed in your Symfony2 developer’s toolbar.

Sample usage of Stopwatch Component to profile connection to Twitter API

Note that in Production environment (with debug=false) Stopwatch isn’t initialized so we have to use on-invalid="null" in service configuration

2. Beware of forms

Symfony Form Component is one of the most complex feature of the whole Symfony stack. It can handle almost every use case you’ll likely need. Including collections of fields, sub-forms which depend on the values from the other fields, conditional validations based on the form values and much more.

The Form Component evolved since last years. Symfony Contributors have made great work to enhance it even further. We’re observing another features implemented to it and also the work done for optimizing performance of such complex component.

The performance problems with Symfony forms were visible in the first versions of 2.* branch. Nowadays it isn’t mostly anything crucial. Of course, you must be aware that they will add extra milliseconds of loading time but that is a cost of every next complex component (think ORM).

We think, that the benefits of the possibility of faster development, are worth some performance loss.

But still, to give you some advice — don’t create multiple forms in one page – creating forms in a foreach loop is as bad idea as running SQL queries in it.

3. Use HTTP Cache and fragment caching

Caching is the thing that obviously led to the most performance gains. As closer the Caching will be to the end-user, as faster the site will load.

Using Varnish and ESI (for fragments caching) will do the most work for optimizing your application. To not reinvent the wheel, we just suggest you to read the dedicated section from the Symfony Book – HTTP Cache.

4. Always use Composer’s class-loading optimization

Use php composer.phar install -o for the best class loader performance.

Internally, default class loader uses file_exists function to check for the existence of the class in specified file based on its name and namespace.

Using --optimize (-o) option, Composer is building a big association array with FQDNClassName => Path/To/ClassName.php mapping. That results in a lower number of calls for file_exists for vendor classes.

Drupal team insights on optimized ClassLoader

5. Enable a Byte Code Cache and ApcClassLoader in app.php

Enabling APC is the first thing you should do to optimize the speed of your PHP application. APC caches PHP to the byte code so that it isn’t necessary to always recompile PHP files.

To even enhance the Symfony, you should enable the ApcClassLoader (located in web/app.php). Internally, it uses APC to store the paths of the classes.

6. Add common classes to compile on cache warmup

On the first run, Symfony2 is building a big cache of commonly used classes. The resulting file can be found in app/cache/{env}/classes.php. It can optimize the IO in the class loading process reducing the number of files needed to be open.

Using Symfony\Component\HttpKernel\DependencyInjection\Extension class in your own bundle, you can add your classes to that cache.

It’s as easy as adding:

$this->addClassesToCompile(array(
    'FQDN\\ClassName',
));

to your Acme\MainBundle\DependencyInjection\AcmeMainExtension class.

Remember that it won’t work for classes for which you declare configuration as annotations (eg. ORM’s @Entity or Validation).

Extension::addClassesToCompile

Photo by Titus Hageman


Are you looking for an experienced Symfony2 developers?
Check out our offer!

Looking to scale-out your
web application?

Hire Octivi!

Antoni is a Software Architect and Scrum Master at Octivi. He is responsible for software architecture of our key projects, he also holds Professional Scrum Master certificate.

  • Thx for the article but in my opinion as someone who looking for practical tips it’s too theoretical imo. But thank you! I found second block with forms interesting did not know about form changes between versions 2.1 and 2.4. Will read it later. I would like to raise a question pointing to the fourth point. I thought that when i install my vendor libraries with php composer.phar install it will install files to the /vendor folder that’s right yea? So now i should run it one more time with -o flag so the composer could generate a big association array with classes? I dont understand to this point. I thought that symfony2 in prod env after first load cache all files and they are then just quickly accessed from cache folder. So what’s the difference?

    Also when i have runed → php composer.phar install -o this appear in console:

    Nothing to install or update
    Generating optimized autoload files
    Clearing the cache for the dev environment with debug true
    Installing assets using the hard copy option
    Installing assets for SymfonyBundleFrameworkBundle into web/bundles/framework
    Installing assets for FOSJsRoutingBundle into web/bundles/fosjsrouting
    Installing assets for TBWelcomePageBundle into web/bundles/welcomepage
    etc all my assets like i would run

    php app/console assets:install –env=prod

    Not sure what this command actually do…and what is the point of symfony2 cache folders then.

    And the same apply for point 6.

    I am not sure how now it all works together and the cache warmup. Aren’t the classes all loading from symfony2 cache folders or…?

    I am new to symfony2 so i hope it’s not a silly question and i will be happy if someone would have time to explain it. Thank you for article and looking forward to next one. For example some really good explanation about using ESI with each user different private content etc? 🙂

    • Sebastian Stok

      The composer class-loading optimization and Symfony cache are two completely separate things.

      The Symfony cache is generated for templates, translations and such. But also to generate a classes.php file with some classes preloaded (which increased performance when APC).

      Btw. You need be very cautious with using this as its will also include any interface of other class your using/referring in the in the class you want to cache, so the the eventual list can very big and remove the original performance gain. Only add classes that are small of have low coupling to other classes and you use at least 80% of the time.

      The composer optimize-autoloader is to speed up the composer autoloading process, it generates a class-name to file mapping removing the need to check this for every-class every time. The ‘-o’ is just a flag you pass with the command.

      https://getcomposer.org/doc/03-cli.md#install

  • Great article, as always!

    I’d like to share a trick with you: NullObject. It will alow you to skip null checks on the StopWatch service.

    For more information, here’s a nice article from Richard Miller: http://richardmiller.co.uk/2014/03/17/avoiding-optional-dependencies/

    • Hi! Thanks for pointing it out 🙂 I’m aware of that not fancy-looking null checks, but unfortunatelly Symfony2 doesn’t have any NullStopwatch object to inject instead :/
      Btw. There is a Monolog’s NullLogger provided which fit great for such situations but nothing in Stopwatch component 🙁

      • I’m sorry, I didn’t know there wasn’t any NullStopWatch implementation. This does seem weird, I wonder if it would be possible to propose one

  • mihey

    How do you optimize your forms? I think that bottleneck in the form processing is `createView()` method on the Form component. `createView()` for one of my forms takes 50% (1 sec.) of the whole controller processing time.

  • Sergey Zolotov

    Now in prod env stopwatch is available. Is it a good idea to use it in the production env?