Using the ClassMap Autoloader for Better Performance

Zend Framework 2 Forms

Do you want a simple way to improve the performance of your Zend Framework 2 application? One that you don’t need to code and can be maintained for you at deploy or commit time? If so, come learn about the ClassMap autoloader.

Zend Framework 2′s been critiqued many times as being slow, at least slower than some of the other leading PHP frameworks. And to be fair, sometimes it’s true. But it doesn’t need to be and there are simple things you can do to improve performance of your applications.

So this post will be the first in a multi-part series looking at ways in which you can improve the performance of your Zend Framework 2 application, with only a minimum of effort.

Today, we’re looking at the 2 autoloaders which are available in Zend Framework 2; these being the StandardAutoloader and ClassMapAutoloader.

What is an Autoloader?

If you’re not familiar with autoloaders, they’ve been around since PHP 5 and allow classes to be found by PHP when either the new() or class_exists() functions are called. You almost no longer need to make use of the require() and include() functions any longer.

PSR-0

Both the Standard and ClassMap autoloaders support PSR-0 compliant autoloading. Quoting the standard, that means:

  • A fully-qualified namespace and class must have the following structure <Vendor Name>()*
  • Each namespace must have a top-level namespace (“Vendor Name”).
  • Each namespace can have as many sub-namespaces as it wishes.
  • Each namespace separator is converted to a DIRECTORY_SEPARATOR when loading from the file system.
  • Each _ character in the CLASS NAME is converted to a DIRECTORY_SEPARATOR. The _ character has no special meaning in the namespace.
  • The fully-qualified namespace and class is suffixed with .php when loading from the file system.
  • Alphabetic characters in vendor names, namespaces, and class names may be of any combination of lower case and upper case.

So if I’d created a module called VideoManager and in that had requested VideoManager\Notify\Video\EmailNotifier, assuming VideoManager is a module I’ve created, then the class will be found in module/VideoManager/src/VideoManager/Notify/Video/EmailNotifier.php. By default, the Zend Framework libraries will be found under vendor/zendframework/zendframework/library.

The Standard Autoloader

As the name infers, it’s the standard autoloader and if no other is used it will be the fallback one used to look up the class in the available paths.

This is fine in development, as you’re experimenting and testing ideas and performance isn’t an issue. But in production, you could have a rather heavy performance penalty if you continue to use it.

The reason is that it has to search all the paths which are available to it, such as the include_path and all the module paths, when attempting to resolve the file. Depending on the number of paths available and the directory depth , this could take some time.

The Configuration

By default it’s setup for each module in Module.php in the getAutoloaderConfig() function, which I’ve got a sample of below.

php
public function getAutoloaderConfig()
{
    return array(
        'ZendLoaderStandardAutoloader' => array(
            'namespaces' => array(
                __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
            ),
            'fallback_autoloader' => true,
        ),
    );
}

Here you see that it returns an array of autoloaders, specifying only one, Zend\Loader\StandardAutoloader. It specifies a namespace, which is the current module, and a path where the files for that namespace can be found, which will be module/VideoManager/src, assuming the module name is VideoManager.

Let’s run Apache Bench on that configuration and see how it performs. The configuration I’m using is 200 concurrent users over 15 seconds, using the following command: ab -c 200 -t 15 http://localhost:8080/.

I’m using the PHP cli server with only a simple site and connecting to a SQLite3 database. So there’s not a lot of overhead. Here’s the results.

Finished 118 requests
Document Length:        5584 bytes
Time taken for tests:   15.114 seconds
Complete requests:      118
Failed requests:        0
Write errors:           0
Total transferred:      695020 bytes
HTML transferred:       658912 bytes
Requests per second:    7.81 [#/sec] (mean)
Time per request:       25617.441 [ms] (mean)
Time per request:       128.087 [ms] (mean, across all concurrent requests)
Transfer rate:          44.91 [Kbytes/sec] received

You can see that it’s ok at 7.81 requests per second. Let’s see if that can be improved.

The ClassMap Autoloader

This autoloader is a much higher performing autoloader than the standard. The reason being, is because it doesn’t search the filesystem to attempt to resolve a class. Instead, as the name implies, it uses a class map, or a map of classes to their file paths to resolve the file. Here’s a small sample of what one looks like:

<?php
return array(
 'VideoManagerTablesFactoriesVideoTablegatewayFactory' => __DIR__ . '//src/VideoManager/Tables/Factories/VideoTablegatewayFactory.php',
  'VideoManagerTablesVideoTable' => __DIR__ . '//src/VideoManager/Tables/VideoTable.php',
);

On the left you see the class, on the right you see the path. The array can then be searched with a isset(). In production, you could expect this to be significantly faster.

Enabling the ClassMap Autoloader

To enable it, in getAutoloaderConfig() we add the following to the start of the returned array:

'ZendLoaderClassMapAutoloader' => array(
    array(
        __DIR__ . '/autoload_classmap.php'
    )
),

This will add it as the first autoloader to be used and tell it to use the classmap which we’ve just generated.

Generating the Class Map

But how to keep the file up to date? You don’t want to do this by hand, for obvious reasons. But gladly you don’t have to. A utility is shipped with Zend Framework 2, which does this all for you, classmap_autoloader.php and is available in the bin directory.

This will scan a directory for all the class files and generate the classmap file automatically. Several options are available including:

  • How the file will be created. If it exists, it can be overwritten or appended to
  • Where to search for the class files
  • Where to write the classmap file to
  • Whether to sort the results

Assuming I’m in the module directory, running the command below will generate a new classmap file, called autoload_classmap.php in the root of the module.

../vendor/bin/classmap_generator.php --library VideoManager --output VideoManager/autoload_classmap.php --overwrite --sort

Let’s now re-run Apache Bench to see what the performance difference is.

Finished 123 requests
Document Length:        5584 bytes
Time taken for tests:   15.189 seconds
Complete requests:      123
Failed requests:        0
Write errors:           0
Total transferred:      724470 bytes
HTML transferred:       686832 bytes
Requests per second:    8.10 [#/sec] (mean)
Time per request:       24697.231 [ms] (mean)
Time per request:       123.486 [ms] (mean, across all concurrent requests)
Transfer rate:          46.58 [Kbytes/sec] received

Wrapping Up

Ok, so the differences weren’t mind boggling, 7.81 requests per/second versus 8.10. But this is a simple test setup. But I do encourage you to try it out and see what your results are like. If you’d like more comprehensive information about the classloaders and aren’t using them in the full MVC stack, then defeinitely check out Rob Allens excellent post or this post by Hari K T.

Otherwise, if you’ve got any thoughts or questions, add them in the comments.

Performance
  • http://mwop.net/ Matthew Weier O’Phinney

    You might also be interested in my post, “Autoloading Benchmarks,” (http://mwop.net/blog/245-Autoloading-Benchmarks.html) which basically formed the rationale for creating the ClassMapAutoloader in ZF2 in the first place. One thing to note: with an opcode cache, usually the autoloader does not come into play after the first few requests, as the opcodes will be picked up (as I note in the article, it’s a micro-optimization in most production stacks). That said, with the sheer number of classes present in ZF2 on any given request, the classmap strategy definitely improves things for those initial class loads.

    One other point: you can use Composer to achieve this to a large degree as well, particularly if you are using Composer to install the initial skeleton application. When you run “composer install” to create your production package, add the “–optimize-autoloader” switch, which essentially creates a class map of all classes in all vendor packages. You can take this a step further by adding autoloader entries for each ZF2 module you create as well — which means that in production, these will also be part of your class map.

    • http://www.matthewsetter.com/ Matthew Setter

      Hi Matt, thanks for such a comprehensive update and clarification on the key points that I mentioned in the article. Those points about Composer are something that wasn’t as aware of as I potentially should be. Thanks for the link to your post as well, I’ll check that out shortly.