Speeding-up autoloading on PHP 5.6 & 7.0+ for everyone

By Nicolas Grekas, on Apr 11, 2016

As the CTO of Blackfire, I do a lot of performance profiles: both to enhance the product and just as a regular user who wants to understand ones software behavior. What I’ve learned profiling so many PHP apps is that autoloading takes a significant amount of resources.  Of course, this number varies a lot and that’s maybe not what you’ve seen in you own app, but a typical 10% wouldn’t be surprising.

Nowadays, almost everyone uses composer to install their dependencies, don’t you? This means that composer’s ClassLoader implementation is likely the most universal piece of code running on the PHP engine, all versions included. If you follow established best practices (or Blackfire’s own recommendations), you know that dumping the classmap (in authoritative mode) generates the fastest autoloader: it reduces autoloading to an isset() check, one of the fastest in PHP.

But this classmap doesn’t scale when your app gets bigger. Even with OPcache enabled, the classmap is a plain PHP array that becomes slower to load when you add more classes to it…

But that was before. Before PHP 5.6/7.0 implemented immutable arrays and constant expressions. Before composer went patched to take advantage of them.

On PHP 5.5 or with a non-patched composer script, the classmap (and all smaller internal maps generated by composer) are loaded in memory for each request. For each request, these classmaps are executed, and for each request they take a few MB of memory. This is true even if you have OPcache enabled, because in this case OPcache is only able to save the compilation of the classmaps. It can’t save the execution of the byte-codes that construct the corresponding PHP arrays.

Here comes PHP 5.6 and constant expressions. Thanks to them, PHP can concatenate strings at the compilation stage. For composer, this means that the path of directories can be constructed using interned strings declared as e.g. __DIR__.'/../symfony/symfony' instead of the not-possible-to-optimize $vendorDir.'/symfony/symfony' dynamic version. Add immutable arrays to the mix and we’ve a winner: when an array is declared as the default value of a property or as a constant (yes, a const can hold an array starting with PHP 5.6), then OPcache is able to keep the array in shared memory and use copy-on-write to save duplicating this array in each requests’ memory space.

By patching composer to use these immutable arrays, we measured a 10% autoloading performance increase and saved 30% of peak memory usage in our test scenario, which consisted of using Blackfire’s classmap (~8300 lines) and load a subset of ~420 classes from it, this number being a typical one for a single web request. Here is the resulting Blackfire profile:

As you can see, composer doesn’t load the autoload_classmap.php file anymore, and this is where most of the gain comes from.

At Blackfire.io we love Open-Source. We’re thrilled to contribute a performance enhancement that will benefit almost everyone in the PHP community, and kill one argument against adding more dependencies to your composer.json file.

Enjoy!

Nicolas Grekas

Nicolas worked as Blackfire's CTO. He studied at ESPCI Paris 12 years ago and holds a MS in physic and chemistry. He also studied entrepreneurship at HEC-Entrepreneurs; a skill he applied immediately as co-founder of the Webistem company, developing SaaS products for the KM area. Active Open-Source contributor in the PHP world since years, he is an active Symfony core-team member.