Redbeard Creations Redbeard's home on the web

Benchmarking the PHP Classname Resolution Keyword

Posted on in programming

Recently I had need to determine roughly what happened (and how quickly) when you use the ::class class name resolution keyword in PHP. If you don't know, this special use of the class keyword was added to PHP 5.5 to provide an easy means of determining the name of a class. It is frequently recommended for use in dependency injection containers. So, how does it perform? This post attempts to address that. But first, some background.

Quite some time ago, I added a dependency injection container to the legacy project I maintain. Specifically, I set up Aura.Di, although that's not really relavent to what I'm talking about today. What is relevant is how I was specifying classes to the container.

Initially I followed the documentation. It said to use strings to specify the class names of services and objects to construct. So I was using something like

// String class name
$di->set('My\Project\Service', $di->lazyNew('My\Project\Service'));

Now, that's somewhat redundant. But that's not the point here. The point is, I have seen other recommendations (for other DI containers) that say to use the ::class keyword. Like so:

// Class keyword
$di->set(My\Project\Service::class, $di->lazyNew(My\Project\Service::class));

or, less verbosely but maybe more clearly:

// Class keyword
use My\Project\Service
$di->set(Service::class, $di->lazyNew(Service::class));

There are many benefits to be had with this second method: IDEs can help with autocompletion, you can take advantage of use statements, you aren't relying on magic strings, and probably more. At first I was hesitant to use it though. What if PHP autoloads every class specified this way? At that point I already had a couple dozen classes specified in my configuration. Autoloading every one of those classes on every page load could be disastrous.

So, I created a quick test:

// Test ::class keyword
echo This\Is\My\Nonexistent\Object::class;
echo "\n";

With no autoloading installed, that should fail if ::class tries to autoload the class. But it didn't fail. PHP was quite happy to show the name of a nonexistent class. Awesome! I started switching over immediately. A couple weeks ago our main development branch had most of the DI configuration switched over to using ::class.

If I had read the PHP documentation, I would have known that:

The class name resolution using ::class is a compile time transformation. That means at the time the class name string is created no autoloading has happened yet. As a consequence, class names are expanded even if the class does not exist. No error is issued in that case.

Of course, I'm a firm believer that you can learn by reading, but you can retain more of it by doing. So my efforts were not totally wasted.

But I was still curious about one more thing. Is there any sort of speed penalty versus using the raw strings? It doesn't really matter, I'm going to stick to using ::class for all benefits mentioned above. But…how much of a penalty is it?

To that end, I created ① a quick test script and ② a repository to house it (and any other experiments I might wish to set up). And then I ran the test script.

Please note: this is a microbenchmark. I advise you to take the actual numbers with a grain of salt. Realize that these are the numbers from my laptop with the script run at a particular time with a particular set of programs running. Most likely they will be different if I run them again. And they will be different if you run them on your set up. However, the relationships between the numbers should carry to most environments.

I ran the test script with 10,000,000 iterations on PHP 5.6.21 and PHP 7.0.6 on my laptop, running Debian sid/stretch. I made five runs with each PHP version and threw out the lowest and highest results for each. And I came up with the following (timings in seconds).

Test PHP 5 PHP 7
Strings 3.1123 0.8149
Full Namespace 3.1302 0.8173
Short Namespace 6.2273 1.6559

You can see that PHP 7 is an order of magnitude faster than PHP 5. That's to be expected. PHP 7 is faster at pretty much everything. More interesting is the differences in the different ways of specifying the class name. Putting in a use statement and using the shortened version of the class took roughly twice the time as more fully specifying the class. And using the ::class keyword was ever so slightly slower than using a string.

For me, this means I'll be using more fully qualified namespaces in my DI configuration. I'll probably use shortened namespaces (via use) as well, but probably more selectively.