Speeding-up autoloading on PHP 5.6 & 7.0+ for everyone
At Blackfire.io we love Open-Source. We’re thrilled to contribute a performance enhancement that will benefit almost everyone in the PHP community.
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…
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.