Skip to content
Muhammet Şafak
tr

The fixed cost of installing a PHP framework: disk size tells you nothing

How many megabytes on disk, how many files per request and how many milliseconds on the first request do seven PHP frameworks cost — and which of those numbers actually predicts throughput under load?

Finding

Disk size predicts nothing: Yii2 has the largest vendor tree at 34.1 MB and loads only 62 files per request, among the fewest in the field. Files per request predicts nothing either: CodeIgniter loads 96 files and serves 6,431 req/s, Symfony loads 224 and serves 13,067. The one number that genuinely separates them is the first request served with a cold opcache: 2.2 ms for Phalcon, 72.2 ms for Laravel — thirty-three times. That is the compile bill the first visitor pays after every deploy, and it is measured in tens of milliseconds, not kilobytes.

Cold first request, slowest
72.2 ms
Cold first request, fastest
2.2 ms
Largest vendor tree
34.1 MB
Most files per request
359

Method

The same images, the same PHP 8.4.24 build and the same production set-up as the load measurement. For each candidate a fresh container was started and the following recorded: the package count in composer.lock, `du -sb vendor`, `find vendor -type f | wc -l`, the duration of the **first** request the fresh container serves (opcache empty — cold), the duration of the same request after a 5-second warm-up (warm), and on that request `get_included_files()` and `memory_get_peak_usage(false)`. Boot time is measured from the first line of the front controller to the point the response is produced. `composer install` time was taken as the median of three runs in a container with a pre-warmed package cache, using `--no-dev --optimize-autoloader --classmap-authoritative`, so it measures installation work rather than download speed. Every counter here is deterministic and reproduces exactly; installation time is the only noisy column.

High confidence Repeated runs, controlled environment, raw data published.
Measured on

measured 3 days ago

Published

Environment

PHP
8.4.24 NTS · opcache on · JIT off
opcache
validate_timestamps=0 · 256 MB · no preload
Composer
2.x · --no-dev --optimize-autoloader --classmap-authoritative
Server
nginx 1.29.1 + php-fpm · pm=static · 8 workers
Hardware
Apple M4 Pro · 12 cores · 24 GB · macOS 26.6.1
Virtualisation
Docker Desktop 29.7.2 · 12 vCPU · aarch64

Technologies

Laravel Symfony CodeIgniter Yii2 Phalcon Laminas Slim PHP Composer Docker

To reproduce

STAMP=$(date -u +%F) ./bench/static-metrics.sh

The load measurement says how many requests a second each framework serves. This record chases a quieter question: what does installing that framework cost you when nothing is happening? How much disk does it occupy, how many files does it open per request, and what does the first visitor after a deploy pay?

The practical version of the question is: which of these numbers can you look at to decide that a framework is “heavy”? The answer turned out to be shorter than I expected.

Size on disk predicts nothing

Candidate Packages vendor Files Loaded per request Peak memory composer install
Phalcon a C extension — nothing to install 0 0 MB 12 7 502 kB 0.2 s
Slim 12 0.9 MB 229 79 398 kB 0.5 s
CodeIgniter 3 4.6 MB 781 96 535 kB 0.6 s
Laminas 23 7.7 MB 1,362 269 607 kB 0.6 s
Symfony 51 15.8 MB 3,362 224 517 kB 0.9 s
Laravel 76 29.6 MB 6,447 359 703 kB 1.2 s
Yii2 largest vendor, fewest files loaded 25 34.1 MB 2,927 62 815 kB 0.9 s
Ordered by vendor size. The last two columns do not move together: Yii2 writes the largest tree to disk and opens the fewest files in the field per request.

The most instructive row is Yii2. Its 34.1 MB vendor tree is the largest here — larger than Laravel’s — and yet it loads only 62 files to serve a request. Laravel loads 359, Laminas 269, Symfony 224. Most of what Yii2 writes to disk never sits in the path of a request.

The reverse holds too: CodeIgniter ships with just 3 composer packages, the leanest dependency tree in the field. That leanness does not turn into speed.

Files per request does not predict it either

Files loaded while serving one request

Intuition says fewer files means faster. CodeIgniter serves 6,431 req/s on 96 files; Symfony serves 13,067 on 224 — twice the files, twice the requests.

lower is better Source: get_included_files() on a warm container, single request

Data table
get_included_files() on a warm container, single request
Series PhalconYii2SlimCodeIgniterSymfonyLaminasLaravel
files loaded 7627996224269359

The chart is drawn in the browser; the table below carries the same data.

Put the load figures next to this table and the relationship falls apart. CodeIgniter loads 96 files per request and serves 6,431 req/s; Symfony loads 224 and serves 13,067. The framework opening more than twice as many files serves more than twice as many requests.

The reason is opcache. With validate_timestamps=0 a file is compiled once and stays in shared memory, so on later requests “loading” it means binding to ready opcode rather than reading from disk. File count is therefore cheap on the warm path. To see where it is expensive, you have to empty the opcache.

Where the difference actually is: the cold first request

Duration of the first request a fresh container serves (opcache empty)

The compile bill the first visitor pays after every deploy. This is what Phalcon does not pay: the framework is already inside the PHP binary.

ms lower is better Source: Fresh container, first HTTP request, front controller to response

Data table
Fresh container, first HTTP request, front controller to response
Series PhalconSlimYii2CodeIgniterLaminasSymfonyLaravel
cold first request 2.2 ms7.9 ms17.4 ms22.2 ms27.7 ms35.7 ms72.2 ms

The chart is drawn in the browser; the table below carries the same data.

The ratio between cold and warm gives the compile bill directly:

Candidate Cold first request Warm request Ratio
Phalcon 2.2 ms 0.41 ms
Slim 7.9 ms 0.35 ms 23×
Yii2 17.4 ms 0.45 ms 39×
CodeIgniter 22.2 ms 1.04 ms 21×
Laminas 27.7 ms 0.79 ms 35×
Symfony 35.7 ms 0.69 ms 52×
Laravel 72.2 ms 1.26 ms 57×
The same request, the same container. The only difference is whether opcache is populated.

In a pool of eight workers that means close to half a second of accumulated latency across the first eight requests after a Laravel deploy. On a busy site it shows up as a brief spike and disappears; on a page that gets a handful of visitors a day, the first visitor pays it every time.

Peak memory: the order nobody expects

The most inverted result in the table is memory. Laravel, which loads the most files per request (359), peaks at 703 kB; Yii2, which loads the fewest (62), is the highest in the field at 815 kB. File count misleads here too: memory depends on how many objects those files construct, not on how many were opened.

Slim is lowest at 398 kB, as expected from how few objects it builds. Phalcon’s 502 kB is the interesting one: it runs almost no PHP code, yet still constructs its objects in PHP’s memory space.

Findings that did not fit the table

  1. Phalcon’s composer install takes 0.2 s because there is nothing to install. The project has 12 files and its composer.lock carries an empty package list; the framework is a C extension compiled into the PHP binary. The trade is on the other side: upgrading is not composer update, it is compiling an extension on the server.
  2. ext-psr forced the shared image to fork. Phalcon requires it, and with it loaded Symfony is fatal on PHP 8.4. Phalcon runs on its own runtime image; the PHP version, opcache settings, nginx and php-fpm pool are byte-identical, and only those two extensions differ.
  3. ext-mysqli is installed for all seven because CodeIgniter 4 has no PDO driver. The other six never use it; it is there so the extension set stays identical.
  4. Laminas’s official skeleton has not been released for PHP 8.4. laminas-mvc-skeleton 2.4.0 declares ~8.1.0 || ~8.2.0 || ~8.3.0 and refuses to install without --ignore-platform-req=php. So the 23 packages and 1,362 files in the table were installed on a version that is not officially supported.
  5. Most of Yii2’s 34.1 MB is bower and npm assets. The basic skeleton pulls jQuery and Bootstrap into the vendor tree; none of it is used by the routes in this measurement.
  6. composer install times were measured with a warm package cache. On a cold cache the figures would track network speed and say nothing about the framework. The median of three runs is reported; this is the only noisy column in the table.
  7. opcache preload was not enabled for any candidate. CodeIgniter ships a preload.php, and there are well-known recipes for Laravel and Symfony. Enabling it changes the cold-first-request figures fundamentally — but it is not a floor all seven share, so it was left out of scope. It deserves its own measurement.
  8. Image sizes do not separate the frameworks. They range from 486 to 518 MB and nearly all of that is the shared PHP base image; the vendor tree is small beside it. That is why the column is not in the table, though it is in the raw data.

Related posts

Share:

Other records

All records

opcache preload cuts the deploy bill by up to fourteen times — but five of seven frameworks do not hand it to you

With `opcache.preload` on, how long is the first request seven PHP frameworks serve after a deploy, what does the gain cost, and who can actually have it?

Finding

Preload shortens the cold first request by between 3.5× and 14.2×: Symfony drops from 35.58 ms to 2.50 ms, down to Phalcon's bare figure. But only two of the seven candidates — Symfony and CodeIgniter — publish a preload file of their own; for the other five the gain sits on the table waiting for the user to write one. Writing one is not as easy as it looks: a preload generated blindly from the classmap never brings Symfony up at all, and on CodeIgniter it does worse (5.29 ms) than the hand-picked official file (3.13 ms). And the cost does not vanish: Laravel's classmap preload takes the 62 ms it saves each visitor and writes it back as 2,340 ms of php-fpm start-up.

measured yesterday

High confidence

Laravel's preload curve: 123 files buy eight times what the last 1,912 do

How far can a curated preload take Laravel, and what does each slice cost in start-up time?

Finding

The curve is not proportional to volume. The first 1,592 files — Laravel's own framework — buy 30 ms and add 1.2 seconds to start-up. The next 1,094 Symfony files buy 9.5 ms for free. The **123 files** after that (psr, carbon) buy 15.7 ms, more than the 1,094 before them. And the last 1,912 buy 1.8 ms while adding another 1.2 seconds. So the blanket preload the earlier record measured as a ceiling is the worst point on the curve that is not the origin: stopping at 2,809 files gives 12.77 ms for 1,514 ms of start-up, while 4,721 files ask 2,691 ms to reach 10.96 ms.

measured yesterday

High confidence

The PHP ecosystem does not wait for a new release — but it does not declare support either

After a PHP release ships, when do the 500 most-installed Composer packages declare that they support it, and how much does that declaration actually say?

Finding

43.5% of installs arrive on a constraint with no upper bound at all — `symfony/console` says `>=8.4.1` today, which claims support for PHP 12 as well. Of the 280 packages that do close the top, 247 made the commitment before the version existed: `guzzlehttp/guzzle` covered 8.4 in October 2020, four years early. That leaves 33 packages that genuinely waited, at a median of 325 days. The raw medians fall version over version and read as an ecosystem speeding up; restricted to an equal observation window the trend reverses (238 → 215 → 325 days).

checked yesterday

High confidence

Search the site

Start typing to search posts, projects and pages.

Esc to close Powered by Pagefind