Comparing PHP’s Autoload

Jorge Castro
Cook php
Published in
5 min readMar 4, 2019

I created a empty blog on Laravel. The project is empty but the default libraries and components, i.e. there is nothing here but the boilerplate of Laravel.

Files:

7545 files in total. (including files that aren't PHP files)

Note: Laravel requires 7000 files for a simple hello world. Are they kidding us?

Anyways, I’m not talking about Laravel (yet) but about autoload.

For this project, I will use two autoload, the autoload of Composer (that it’s the default autoload of Laravel and a custom library that I created)

AutoLoadOne

It’s a free library that generates a single autoload.php file. It has a interface visual and it doesn’t care if the project is PSR-0 or PSR-4, it works in any case. It even works if there are more than two classes in the same file or if the class doesn’t have namespace.

How it works?

  • Copy the library on the root project.
  • Execute the page.
  • That’s it.

So, I generate a Laravel project, I run the autoload generator and it returns the next values:

Number of Classes: 5565
Number of Namespaces: 765
Number of Maps: 2305 (you want to reduce it)
Number of PHP Files: 6302
Number of PHP Autorun: 0
Number of conflicts: 31

And it generated the next file:

autoload.php 231kb.

So, finally we could use it as follow

<?php

include "autoload.php"
;

// our code

There are two values that we want to optimize: the size of the autoload and the number of “maps” (or elements on the dictionary). In my case, it is:

Number of Maps: 2305 

It means that the autoload recognize 2305 namespaces + classes. However, we don’t need all classes or namespaces. So we could optimize it a bit.

Optimized AutoLoadOne

I separated PHPUnit and Mockery from the autoload. Why?. Both libraries are for unit test so they aren’t executed on runtime. And maybe we could exclude other libraries/folders. We can generate a different autoload that includes all but for this case, PHPUnit and Mockery are out.

excluded namespace = /vendor/phpunit/,/vendor/mockery/

And it is the result:

Number of Classes: 5565
Number of Namespaces: 765
Number of Maps: 1535 (you want to reduce it)
Number of PHP Files: 6302
Number of PHP Autorun: 0
Number of conflicts: 13

File generated:

autoload.php 159kb.

Composer’s autoload (using optimize)

Now, we will use Composer’s autoload to generate the autoload’s map. We will use the “-o” (optimized) flag.

composer dump-autoload -o

Generated optimized autoload files containing 3519 classes
Number of Maps: 3519 (static and not-static)

So Composer’s autoload generates a big map (3519) versus 2305 (not optimized) and 1535 (optimized).

Also, Composers’s Autoload uses one of the next methods:

Static: (fast method, it uses more memory and it requires to be calculated manually)

  • autoload.php 1kb
    autoload_real.php 3kb
    autoload_static.php 468kb
    ClassLoader.php 14kb

Not static: (default method)

  • autoload.php 1kb
    autoload_real.php 3kb
    autoload_namespaces.php 1kb
    autoload_psr4.php 4kb
    autoload_classmap.php 426kb

It generates more files and the sizes are biggest.ddd

Why the size matter?.

Let’s say we are calling a single webpage that uses autoload.

If we use Composer’s autoload (static), we are also calling a file that uses 468kb (plus other files), and this memory is loaded into the memory. It could use (an average of) 609kb of ram per call (it’s around PHP file x 1.3 x 1kb)

For example, what if we have 1000 concurrent users. It will use 609kb x 1000 = 609mb of ram thanks to Autoload alone at the same time and with 10k concurrent users we will use 6gb of ram only because autoload.

With AutoLoadOne, it is optimized to 302mb (1000 users) or 3gb (10k users), it is for the version not optimized.

AutoLoadOne tags all classes from the project, including classes that aren’t defined in composer.json (unless they are excluded from the project). Composer’s autoload found only 3519 classes, while AutoLoadOne found all classes of the project (5565).

However, some classes are not required to be loaded by the project (for example unit test classes), so we could exclude those classes of the project.

For example, excluding PHPUnit and Mockery reduces the use to 206mb (1000 users) or 2gb (10k users) but we could optimize it even further.

Lookup usage?

What is a lookup?. This: $findme=$array[‘somekey’];

Let’s say we have a “map” with different elements. How much time does it takes to find the element of the map?.

So, the size of the map/lookup time is not important. The difference between a small map (100 elements) versus a huge map (1 million of elements) is 0.1 second in total (per 1 millon of queries). However the memory usage matters and it could impact the performance considerably.

How many lookup are called?.

Let’s say we have 10k concurrent users and each one calls 100 different classes. It means we are doing 10k x 100 = 1 million of lookup at the same time.

TEST II (Magento 2.x)

Magento is a huge project, it has 22k PHP files and from it, 20k are classes. However, it generates a map of 9.2k elements (without optimization)

AutoLoadOne:

Number of Classes: 20596
Number of Namespaces: 6868
Number of Maps: 9242 (you want to reduce it)
Number of PHP Files: 22227
Number of PHP Autorun: 0
Number of conflicts: 6
File size 1.06mb

It takes 38 seconds to generate the autoload.php

While using Composer’s autoload (optimized)

Generated optimized autoload files containing 11329 classes
Number of Maps: 11329 (1.6mb file size)

(*) However, Magento wasn’t create for concurrency. But, what we are measuring is not the number of concurrent users but the number of concurrent calls (for example rest-json, opening a page and such).

Code execution.

Both AutoLoadOne and Composer’s autoload execute a code when it is initialized/executed.

When AutoLoadOne generates the map, it consists of two relational arrays as follow:

private $_arrautoloadCustom = array(
'Magento\AdminNotification' => '/app/code/',
'Magento\Notice' => '/app/code/Developer/',...

While Composer’s autoload generates an array that requires concatenation.

array(
'Magento\\AdminNotification' => $baseDir . '/app/code/MagentoActions.php',
'Magento\\Notice' => $baseDir . '/app/code/Developer/Notice.php',

So it requires to concatenate each map (with a variable called $baseDir). So Composer’s autoload affects slighly the performance.

Conclusion:

  • Composer’s autoload could be optimized (or to use a different one like in my case).
  • The number of files COUNTS!. Of course we don’t want a single big file but it’s impractical to create thousand of php file and it has a hidden costs.

--

--