Skip to content
Muhammet Şafak
tr
Tooling comparison

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 2 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
Service & load Measurement

Seven PHP frameworks under identical load: the gap narrows as soon as the request does real work

On the same hardware, the same PHP build and the same seven routes, how many requests a second do Laravel, Symfony, CodeIgniter, Yii2, Phalcon, Laminas and Slim serve, and at what latency?

Finding

On an empty route the fastest is 4.4× the slowest (Slim 25,975, Laravel 5,966 req/s). As soon as the request does real work the gap closes: 3.7× for a single row from the database, 3.5× for twenty rows. Phalcon is third on an empty route and fifth once a query is involved — being a C extension buys nothing while the process waits on MySQL. And the expensive decision is not the framework: Laravel's own default `web` middleware group takes the same response from 5,858 to 2,176 req/s, so one default costs more than most of the distance between the frameworks.

Medium confidence
Frontend performance Measurement

How you import Chart.js decides how many kilobytes the visitor downloads

In a real production build, what is the difference between `chart.js/auto` and a selective `Chart.register()` — in kilobytes?

Finding

Selective registration saves 9.7 kB gzip over `chart.js/auto` (67.8 → 58.1 kB, 14.3%). The larger drop is not in the library core but in leaving unused controllers out: a page that registers only the bar chart falls to 46.0 kB — two thirds of auto.

High confidence

Search the site

Start typing to search posts, projects and pages.

Esc to close Powered by Pagefind