Skip to content
Muhammet Şafak
tr

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.

Symfony, cold first request
2.50 ms −93%
Candidates shipping a preload file
2 / 7
Laravel classmap, fpm cost
+2,340 ms
Hand-picked vs compile-everything
3.13 → 5.29 ms

Method

The same images as the footprint and load measurements, the same PHP 8.4 build, the same nginx + php-fpm stack. Nothing was rebuilt: opcache reads `conf.d` in order, so a `02-*.ini` mounted at run time overrides the `00-bench-opcache.ini` baked into the image. Three variants per candidate — no preload, the framework's own file (found by probing the container, never hard-coded), and a uniform file that compiles every class in Composer's classmap through `opcache_compile_file`. Each variant got a fresh container, and what was measured is **the first request that container ever serves**: the readiness poll is the measurement, because a separate poll spends the first request and leaves a warm one behind. Three repeats, median reported. Preload's cost is measured as the time until php-fpm answers with a 200; that window includes nginx's own start-up, but it is the same constant in all three variants. Cross-check: the `no preload` variant reproduces the cold figures of the independent run published on 20 August to within 6%.

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

measured yesterday

Published

Environment

PHP
8.4.24 NTS · opcache on · JIT off
opcache
validate_timestamps=0 · 256 MB · preload on/off per variant
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 · aarch64
Repeats
3 · median reported
Baseline check
within 6% of the 2026-08-20 run

Technologies

PHP opcache Laravel Symfony CodeIgniter Yii2 Phalcon Laminas Slim Docker nginx Composer

To reproduce

REPEATS=3 ./bench/preload.sh

The footprint measurement left a number behind: on the first request a fresh container serves, Phalcon takes 2.2 ms and Laravel 72.2 ms — thirty-three times. That is the compile bill the first visitor after a deploy pays. The same record’s environment carried one more line: preload: none.

This record closes that line. One variable.

The gain

First request after a deploy, with preload off and on

Each candidate's best variant is shown: their own official file for Symfony and CodeIgniter, the classmap ceiling for the rest.

  • no preload
  • preload on

ms lower is better Source: bench/preload.sh, median of three repeats

Data table
bench/preload.sh, median of three repeats
Series LaravelSymfonyLaminasCodeIgniterYii2SlimPhalcon
no preload 76.31 ms35.58 ms26.7 ms20.75 ms16.98 ms7.46 ms2.68 ms
preload on 13.98 ms2.5 ms3.81 ms3.13 ms4.8 ms1.86 ms1.99 ms

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

Candidate no preload official classmap Best gain fpm cost
Laravel 76.31 ms none 13.98 ms 5.5× +2,340 ms
Symfony the largest gain 35.58 ms 2.50 ms broke 14.2× −40 ms
Laminas 26.70 ms none 3.81 ms 7.0× −7 ms
CodeIgniter 20.75 ms 3.13 ms 5.29 ms 6.6× −12 ms
Yii2 16.98 ms none 4.80 ms 3.5× +13 ms
Slim 7.46 ms none 1.86 ms 4.0× +28 ms
Phalcon classes live in the C extension 2.68 ms none 1.99 ms 1.3× +43 ms
fpm cost = the difference against the no-preload run in how long the container takes to answer. Negative values are measurement noise, not a gain.

The footprint record’s thirty-three-fold gap closes under preload: Symfony lands at 2.50 ms, Phalcon’s bare figure. The difference was never the framework’s weight; it was opcache being cold.

But five of them do not have one

Candidate Official preload Where
Symfony yes var/cache/prod/*.preload.php, produced by warm-up
CodeIgniter yes preload.php, in the starter template
Laravel no
Yii2 no
Laminas no
Slim no
Phalcon no not needed
The files were probed for inside each image rather than listed by hand: the path is a framework decision and moves between versions.

The second column says more than the first. The gain exists, but in five of the seven it is waiting for the user to write it — and the candidate with the most to gain, Laravel at 76.31 ms, is one of those five.

”Compile everything” is not the answer

For a framework without an official file the obvious fix is to walk Composer’s classmap and compile the lot. The measurement refutes that in two different ways.

On Symfony the container never comes up. Compiling 4,721 files at load time runs into classes that resolve environment variables:

NOTICE: PHP message: [critical] Uncaught Exception:
Environment variable not found: "DEFAULT_URI".

Preload runs in php-fpm’s master process; something dying there means the service never starts at all. The harness records it as ready: false.

On CodeIgniter it is worse than the official file. 3.13 ms official against 5.29 ms for the classmap. Forty-odd hand-picked classes beat 479 compiled blindly: preload fills shared memory, and classes nobody needs take up room.

The cost moves, it does not disappear

Preload takes the compile work out of the request and puts it into php-fpm’s start-up. On small preloads that cost is unmeasurable; on the large one it is unmistakable:

Candidate Files compiled Time to first answer Difference
Laravel paid on every deploy 4,721 2,755 ms +2,340 ms
Laminas 1,195 352 ms −7 ms
Yii2 992 352 ms +13 ms
CodeIgniter 479 324 ms −12 ms
Slim 138 359 ms +28 ms
Phalcon 3 362 ms +43 ms
Classmap variant only; Symfony is absent because it broke.

Laravel’s classmap preload takes 62 ms from every visitor and writes 2.3 seconds into the deploy. On a service with steady traffic that is a good trade — the first visitor pays once and nobody after them pays at all. On a service that deploys ten times a day it inverts: twenty-three seconds of start-up delay against 62 ms for a few hundred visitors.

Phalcon turned into the control group

Only 3 files were compiled out of Phalcon’s classmap — its classes are in a C extension, not in PHP. Its gain is also the smallest at 1.3×. That is the check on what the measurement measures: if preload removes PHP source compilation, it should do nothing for a candidate with no PHP source to compile. It did nothing.

Back to the footprint record

That record said disk size predicts nothing and that the one thing which really separates the candidates is the first request paid while opcache is cold. This measurement adds a condition to that sentence: opcache does not have to stay cold — but whether it does depends on whether your framework handed you a preload file.

Related posts

Share:

Other records

All records

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

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.

measured 3 days ago

High confidence

Search the site

Start typing to search posts, projects and pages.

Esc to close Powered by Pagefind