Benchmarking the PHP Classname Resolution Keyword
Posted on in programming php
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.