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.
- Sweet spot: 2,809 files
- 12.77 ms −81%
- Its start-up cost
- +1,132 ms
- What the 123-file slice buys
- 15.7 ms
- What the last 1,912 buy
- 1.8 ms
Method
The same images, the same PHP 8.4 build and the same measurement point as the opcache record — the first request a container ever serves. Curation is a **path filter** over Composer's classmap rather than a hand-picked list of classes: a hand-picked list cannot be reproduced, a filter can. The five variants nest deliberately (framework ⊂ +symfony ⊂ +psr/carbon ⊂ everything) so the curve reads as "add this much more" rather than as four unrelated points. Fresh container per variant, three repeats, median. Preload runs in php-fpm's master process, so its cost is measured as the time until the container answers with a 200; that window includes nginx's own start-up but is the same constant across all five variants.
- Measured on
- Published
measured yesterday
Environment
- PHP
- 8.4.24 NTS · opcache on · JIT off
- Application
- Laravel, production install, classmap-authoritative
- Variants
- path filter over the classmap · five nested slices
- Repeats
- 3 · median reported
- Hardware
- Apple M4 Pro · 12 cores · Docker Desktop 29.7.2 · aarch64
Technologies
To reproduce
REPEATS=3 ./bench/preload-curate.sh The opcache measurement left Laravel between two points: no preload file of its own, a cold first request of 76 ms, and a blanket classmap preload that reaches 14 ms while adding 2.3 seconds to php-fpm’s start-up. That record measured the blanket version as a ceiling. This one draws the curve between the two — and shows the ceiling is a bad place to stand.
The curve
Cold first request against the number of files preloaded
The five variants nest: each point adds a slice to the one before it. The horizontal axis is files compiled.
ms lower is better Source: bench/preload-curate.sh, median of three repeats
Data table
| Series | 0 | 1,592 | 2,686 | 2,809 | 4,721 |
|---|---|---|---|---|---|
| cold first request | 68.37 ms | 38.01 ms | 28.48 ms | 12.77 ms | 10.96 ms |
The chart is drawn in the browser; the table below carries the same data.
| Slice | Compiled | Cold request | fpm ready | Marginal gain | Marginal cost |
|---|---|---|---|---|---|
| no preload | 0 | 68.37 ms | 382 ms | — | — |
| laravel/framework | 1,592 | 38.01 ms | 1,553 ms | −30.4 ms | +1,171 ms |
| + symfony | 2,686 | 28.48 ms | 1,538 ms | −9.5 ms | free |
| + psr, carbon the sweet spot | 2,809 | 12.77 ms | 1,514 ms | −15.7 ms | free |
| everything | 4,721 | 10.96 ms | 2,691 ms | −1.8 ms | +1,177 ms |
Not how many, which ones
The curve’s shape is not proportional to volume, and that is the finding.
123 files buy eight times what the last 1,912 do. The psr and carbon slice
adds 123 files and takes the cold request from 28.48 ms to 12.77 ms. The 1,912
files after it take only 1.8 ms.
The reason is date handling: Laravel’s first request pulls on Carbon’s loading chain, and that chain is deeply linked — without preloading it gets resolved again on every cold start. A framework being “heavy” is not about how many files it has; it is about which tree the first request walks.
”Compile everything” is the worst choice
The earlier record measured the blanket classmap as an optimistic upper bound. The curve shows it differently: 4,721 files buy 1.8 ms over 2,809 and cost 1,177 ms of start-up. The last slice charges 654 milliseconds of start-up for every millisecond it saves.
For a service deploying ten times a day that is twelve seconds of added start-up in exchange for 1.8 ms for the first visitor. Even on steady traffic the trade is weak.
It does not catch Symfony
Symfony reaches 2.50 ms with its own official file. Laravel’s best point here is 10.96 ms — four times behind, and that is with everything preloaded.
The difference is structural. Symfony’s preload file is generated from its compiled container, so resolving the service definitions has already happened at build time. Laravel’s container resolves at runtime, and preload does not remove that work — it only removes class compilation. Preload saves you from compiling PHP source; it does not save you from the application’s own set-up.
The file Laravel does not ship
What this leaves is not a number but a file: a preload generated with the filter
^/vendor/(laravel\/framework|symfony|psr|nesbot|carbon)/, giving 12.77 ms at
1.5 seconds of start-up — the thing the framework itself does not publish. The
generator is in the repository and runs against any Laravel application.
Related posts
Twelve years with Laravel: growing alongside a framework
From 2014 to 2026, twelve years with Laravel — a story of maturing with a tool, outgrowing it, and choosing it again.
Building Applications with PHP in 2026: State of the Ecosystem
The real state of PHP in 2026: language maturity, ecosystem health, and why the 'it's dead' narrative is still wrong.