Optimizing Composer's autoloader performance

Lately, we spent quite some time optimizing Mouf's performance. It appeared that one of the bottlenecks was Composer's autoload mechanism. Our application was spending quite some time in the autoloader instead of doing interesting things.

I was curious to see how much was spent in Composer's autoloader, so I did a little benchmark with a simple "Hello World!" application in Mouf.

A bit of theory first

Mouf comes with a great number of Composer packages. For my test, I started with the typical 67 packages a Mouf application has (yeah, that's a lot).

Each package relies on PSR-0 autoloading, and in each composer.json of each package, I have this:

"autoload": { "psr-0": { "Mouf": "src/" } }

So basically, each package informs Composer that any class starting with the Mouf namespace might be part of that package. So when Composer encounters a Mouf class (for instance Mouf\Utils\Common\ConditionInterface\AndCondition, it does not know in which package to search for it. It has to run through the 67 packages where it might be. How does Composer search for a class? By performing calls to the file_exists function, in each package. So when the autoloader is triggered for a class in the Mouf namespace, it can do up to 67 calls to file_exists (!). When you know that file_exists is particularly slow on Windows platform, you start to understand why your application could be faster.

Hopefully, Composer comes with the possibility to dump an optimized autoloader:

php composer.phar dumpautoload -o

This creates a "classmap" mapping each class to its file in a big array. When you use an optimized autoloader, you don't perform any calls to file_exists so your application is faster.

Benchmarking the optimization

How faster is an application with an optimized autoloader?

To find this, I wrote a typical "Hello World" application with Mouf and Splash (Mouf's MVC framework). The "Hello World" text is displayed inside a Bootstrap template. Benchmarking is performed using Apache's ab tool, with one concurrent thread and 300 measures. The test is performed in an Ubuntu 13.04 machine, with a Core I7.

Results:

Basic autoloader18.7 ms
Optimized autoloader (php composer.phar dumpautoload -o)11.8 ms (37% faster!)

Conclusion: By optimizing the autoloader, we can have increase performance by 37% (!). On a Windows machine, that would be even higher.

The drawback

The biggest drawback is that the optimization of the autoloader must be performed by the application developer. The application developer might not know that it has to perform the optimization. Furthermore, each time the application developer will perform a php composer.phar update, the autoloader will be reseted to the non-optimized one. And each time a new class is added, the autoloader must be dumped again so the class is added to the classmap generated by the autoloader.

So basically, the responsibility of doing the optimization lies in the application developer hands. As a framework developer, I would like to do as much as possible to avoid that, and as much as possible to have a responsive framework out-of-the-box, even with a non optimized autoloader.

The solution: more specific namespaces

By looking at the way Zend Framework is writing its composer.json files, I realized that you can be very specific on the namespace. You don't have to restrict yourself to the vendor name. You can go further. For instance:

"autoload": { "psr-0": { "Mouf\\Utils\\Common\\": "src/" } }

or

"autoload": { "psr-0": { "Mouf\\Utils\\Logger\\": "src/" } }

This is a very interesting feature, because if all packages are restricted to the the smallest namespace possible, the number of calls to file_exists will be really smaller. So I decided to optimize my 67 composer.json files by selecting the more specific namespace I could.

The results are below:

Results:

Basic autoloader18.7 ms
Non optimized autoloader, but with specific namespaces14.6 ms (22% faster)
Optimized autoloader (php composer.phar dumpautoload -o)11.8 ms (37% faster)

Conclusion

The autoloader's performance is really important in modern PHP applications. Optimizing Composer's autoloader using php composer.phar dumpautoload -o is the way to have maximum performance. But it is the responsibility of the application developer to apply the optimization.

The framework developer can still help the application developer by providing very specific namespaces in each of its composer.json autoload section and by ensuring that each namespace is tied to only one package. This optimization is really worthwhile (22% increased performance in Mouf's case!)

Tags :