Benchmarking PHP Autoloaders

Benchmarking PHP Autoloaders

How much does your autoloader cost?

As I have posted previously, the PHP autoloader is one of the great things about PHP. It truly simplifies PHP development by not having to deal with include files or imports that plague most other languages.

Autoloading is not FREE!

Like everything else, autoloading is not free. There are runtime and memory costs for most autoloaders. Try this code in your project:

$startMemory = memory_get_usage();
$startTime = microtime(true);

include 'vendor/autoload.php';

$endTime = microtime(true);
$finalMemory = memory_get_usage();

$memoryUsed = $finalMemory - $startMemory;
$microSeconds = $endTime - $startTime;

echo "Composer autoload takes {$memoryUsed} bytes of memory\n";
echo "and took {$microSeconds} to load\n";

All this code does is load the autoloader. Nothing else.

For my documentation website PHPFUI, here are the results from my Windows machine, which is an Intel Core i7-8550U CPU @ 1.80GHz running PHP 8.1:

Composer autoload takes 752464 bytes of memory
and took 0.010792970657349 to load

Autoloader Memory Costs

As you can see, the default Composer autoloader is using 752K of memory every time. This number never changes for my benchmark as the autoloader is doing the same thing every time (runtimes can vary based on Windows timing).

That is A LOT of memory just to autoload classes. Think about the number of PHP processes you have running on a server. Each one now loads an additional 752K of memory. Not great.

Autoloader Runtime Costs

Runtimes are harder to benchmark, as it depends on the underlying OS (Windows 11 in my case), and system load among other things. But multiple runs of this program on my machine show time ranges from 0.0102119 to 0.0107929 with no other loads on the system. While this may not seem like a lot of time, it does add up. One hundred requests to your server have just burned up 1 second of CPU time. How many requests a day are you getting? Benchmark your production machine and do the math.

So what to do?

Use The World's Fastest Autoloader!

One of the best things about PHP is the namespace design. PHP namespaces look exactly like a file path in the OS. We can leverage that for our world's fastest autoloader (WFA). If you are using best practices for minimizing supply chain attacks (you ARE doing this, right?), then you have already done most of the work for the WFA.

In this example, I have put all Composer / Packagist sourced code into a directory called ThirdParty from the project root, since I have placed this autoload in the project root. You can find a detailed explanation of how this code works here.

define ('THIRD_PARTY_ROOT', __DIR__ . '\\ThirdParty');
spl_autoload_register(function ($className)
{
    $path = THIRD_PARTY_ROOT . '\\' . "{$className}.php";
    $path = str_replace('\\', DIRECTORY_SEPARATOR, $path);
    if (file_exists($path))
        include_once $path;
});

Now let's rerun our benchmarks with this code:

$startMemory = memory_get_usage();
$startTime = microtime(true);

define ('THIRD_PARTY_ROOT', __DIR__ . '\\ThirdParty');
spl_autoload_register(function ($className)
{
    $path = THIRD_PARTY_ROOT . '\\' . "{$className}.php";
    $path = str_replace('\\', DIRECTORY_SEPARATOR, $path);
    if (file_exists($path))
        include_once $path;
});

$endTime = microtime(true);
$finalMemory = memory_get_usage();

$memoryUsed = $finalMemory - $startMemory;
$microSeconds = $endTime - $startTime;

echo "Composer autoload takes {$memoryUsed} bytes of memory\n";
echo "and took {$microSeconds} to load\n";

WOW! That was FAST

Composer autoload takes 816 bytes of memory
and took 1.8119812011719E-5 to load

As you can see, we reduced almost all the memory usage and got the runtime down to 0.00000181198 seconds. That is pretty impressive!

Now let's try loading some classes

I happen to have 182 classes I can easily autoload with no constructors. I reset the time and memory variables, did a new on each class, then recomputed the time (not worried about memory here, it is what it is for the classes). Your test results will be different from mine depending on what you are loading. My classes do not access the database or any other resources.

Here are my results:

Composer autogenerated autoloader took between 0.049884080886841 and 0.057215929031372 seconds to load.

The world's fastest autoloader took between 0.0505051612854 and 0.056947231292725 seconds to load.

They are close enough to not make that much of a difference at run time. The file loading and PHP initialization are the same for each method. This makes sense, as the Composer autoloader is doing a mapped array lookup and then loading the file. The WFA does some minor string manipulation and then loads the file.

The problems with the Composer autoloader

Besides having a large memory footprint, the Composer autoloader does not have a Big O runtime of 1, like the WFA, but rather log N, which is small, but not 1. As your app grows, the autoloader slows down and consumes even more memory. It is all downhill from the first composer install!

Conclusion: WFA for the Win!

The Composer generated autoloader has a high cost you're paying for on every PHP page served. Reduce your costs by using the world's fastest autoloader!

NEXT: - Packagist Best Practices

PREVIOUS: - Why Use PHP In 2023