Horde Hackathon 2011 part 2

Part 2 of my personal programming projects during the Horde Hackathon 2011 in Boston, featuring an autoloader cache which boosts performance a bunch.

Autoloader caching

It shouldn't be a suprise to any administrator these days that it's a very good idea to install a bytecode cache for PHP if you care about performance, especially if using modular frameworks like Horde. Or any other modern framework. This alone already gets you some impressive performance boost.

While profiling Horde applications I noticed that even after enabling a bytecode cache, a lot of the time is still spent in the autoloader code. It's not spent with loading the class files, but with mapping class names to file system paths. This is not news either and it's the reason why one solution is to provide static class maps. Since most of this time is spent for stat operations on the file paths, disabling stat helps a lot too, at least if possible in your cache of choice.

These solutions don't work well for us for several reasons, so I was looking for an alternative approach. I played with adding caching functionality to Horde's autoloader library. The results have been great. There was one problem though. The autoloader is created as one of the very first actions during a request, long before any configuration is available. I had a few must-haves in mind:

Here's what I've come up with: for broadest backend support (APC, XCache, eAccelerator), I check for any supported extension that doesn't require configuration, and fall back to a file system cache, e.g.:

if (extension_loaded('apc')) {
    $this->_cache = apc_fetch('horde_autoloader_cache');
} elseif (extension_loaded('xcache')) {
    $this->_cache = xcache_get('horde_autoloader_cache');
} elseif (extension_loaded('eaccelerator')) {
    $this->_cache = eaccelerator_get('horde_autoloader_cache');
} elseif (($tempdir = sys_get_temp_dir()) &&
          is_readable($tempdir . '/horde_autoloader_cache')) {
    $this->_cache = @json_decode(file_get_contents(
        $tempdir . '/horde_autoloader_cache'), true);
}

But how to enable and disable it without any configuration? I made this a standalone package that extends and replaces the default Horde autoloader...

spl_autoload_unregister(array($__autoloader, 'loadClass'));
$__autoloader = new Horde_Autoloader_Cache();
$__autoloader->registerAutoloader();

...and is only loaded if it can be found in PHP's path:

if (!@include_once 'Horde/Autoloader/Cache.php') {
    require_once 'Horde/Autoloader/Default.php';
}

To enable the cache the adminstrator excutes:

pear install horde/horde_autoloader_cache

And to disable it:

pear uninstall horde/horde_autoloader_cache
Add some simple console script to empty the cache, if possible. Not all cache extensions work with the PHP CLI SAPI.

Voilą. Thanks to a few lines of code, the great work of the different cache extension projects, the simplicity of installing packages with the PEAR installer, and the easy extendability of the Horde libraries and framework, you get a magnitude of performance boost for free.

A first release candidate of the package will be released tomorrow.

Bits and pieces

A few small things that I've also worked on during the Hackathon: