# Benchmarks Kinetis wins five of six TechEmpower-style benchmark tests against eight other PHP frameworks — Laravel, Laravel Octane, Symfony, CodeIgniter, Yii2, CakePHP, and Slim on two runtimes — measured on isolated AWS hardware on August 18, 2026. TechEmpower sunset its own benchmark project in March 2026, so no neutral third party runs this kind of comparison anymore, which is exactly why the full methodology and every framework's implementation are public in the [benchmark repository](https://github.com/aln-1/kinetis-benchmarks): the numbers below are reproducible, not just reported. This page covers what was tested and what came out, not how the rig is built. ## What was tested Six [TechEmpower Framework Benchmarks](https://github.com/TechEmpower/FrameworkBenchmarks) test types — `/json`, `/plaintext`, `/db`, `/fortunes`, `/queries`, `/updates` — against identical MySQL schema and data, one implementation per framework using that framework's own idiomatic routing, database layer, and templating. Every target runs the same production PHP configuration (opcache with a tracing JIT, timestamps disabled, access logging off) and its own ahead-of-time caching mechanism where it has one. Three separate EC2 instances — application, database, load generator — provide real machine isolation, the same methodology TechEmpower's own benchmark used; its test types remain the de facto standard for comparing web frameworks. Two pairs of targets separate the framework from the runtime it runs on. `kinetis` and `kinetis-fpm` are the identical, unmodified application on FrankenPHP worker mode and on classic nginx with PHP-FPM. `slim-frankenphp` and `slim` are the same pairing for Slim. Slim is a minimalistic "framework": the target is nothing more than a request router and a PSR-7 implementation (`slim/slim` with `slim/psr7`) — no ORM, no templating, no service container, no validation. That is what makes the second pair a control rather than a competitor: `slim-frankenphp` puts that bare router on the same FrankenPHP worker runtime Kinetis runs on, which is the only way to tell how much of a result belongs to the framework and how much to the runtime underneath it. It is excluded from the leader board below and given its own comparison further down. What that comparison shows is worth saying up front: Kinetis carries a full framework on top of that same runtime — attribute routing, validation, dependency injection, a middleware pipeline, OpenAPI generation — and where it trails the bare router it trails by 2.4% on average and 3.8% at worst. Once a request issues more than a handful of queries, it is ahead instead. The fairness rules — what was tuned, why, and what was deliberately left alone — are documented in full in the repository's own README; they aren't repeated here. ## Who leads each test Winner among the eight competing frameworks at the heaviest level of each test, with the margin over second place.

/json · c=256

kinetis

25,676req/s

+95% over slim

/plaintext · c=256

kinetis

25,888req/s

+98% over slim

/db · c=256

kinetis

18,732req/s

+147% over slim

/fortunes · c=256

kinetis

15,689req/s

+120% over slim

/queries · n=20

kinetis

4,538req/s

+5% over slim

/updates · n=20

slim

1,860req/s

+17% over yii2

The same application on classic PHP-FPM tells its own story. `kinetis-fpm` leads every full framework in the field — including `yii2`, the fastest of them — on `/json`, `/plaintext` and `/db`, by around 3%, and is level with the fastest on `/fortunes`. It gives that back on `/queries` and `/updates`: under a boot-and-die runtime Kinetis uses a blocking PDO driver, so a request's concurrent query fan-out has nothing to overlap and pays for the scheduling anyway. Query fan-out is where the persistent worker earns its keep — see {doc}`runtime-adapters`. ## Every measurement Requests per second; higher is better. Bar length is relative to the fastest result anywhere in that test, so a row reads across columns. The highlighted figure leads its column. The last column is p99 latency at the heaviest level. Kinetis rows are tinted; the same-runtime control is marked. ### `/json` Serialize a single JSON object. No database.
concurrency163264128256p99 ms
kinetis23,84824,58525,00525,18125,67622.8
slim-frankenphp24,38925,28025,52725,76626,31021.1
slim11,23612,05312,54712,82013,14143.0
kinetis-fpm9,0259,5799,85710,03410,14960.7
yii28,7309,2719,3819,7389,81261.7
symfony4,8404,9855,0745,0915,09599.4
codeigniter5,7456,0406,1336,2106,19197.9
cakephp5,5395,7895,8855,8925,85591.4
laravel-octane5,5215,5265,4545,2675,020138.4
laravel2,5432,5882,5422,5672,553157.5
### `/plaintext` Return a fixed string. The floor of framework overhead.
concurrency163264128256p99 ms
kinetis23,76624,60724,64324,83525,88822.1
slim-frankenphp24,37825,27225,53825,82626,30321.5
slim11,14612,00912,28812,67913,04950.3
kinetis-fpm9,0699,6489,93610,25210,24656.3
yii28,8369,4369,7779,9859,96863.1
symfony4,8355,0215,0695,1085,123102.9
codeigniter5,6426,0266,1266,1376,14095.5
cakephp5,4855,7625,8945,9645,91489.8
laravel-octane5,5425,5495,4775,3375,119129.2
laravel2,5592,5782,5142,5722,550157.4
### `/db` One random row by primary key, per request.
concurrency163264128256p99 ms
kinetis16,07516,65616,94217,80118,73229.1
slim-frankenphp15,41016,17016,76817,41518,14029.1
slim6,4267,1297,4247,5777,57370.8
kinetis-fpm5,3045,7655,8706,0086,00396.5
yii25,1145,6305,8966,0075,85389.7
symfony3,7153,8613,8923,8893,943111.3
codeigniter2,1652,2432,2292,1332,181172.6
cakephp2,9463,0983,0913,1193,093125.3
laravel-octane4,6534,7804,7584,6794,523151.9
laravel1,9782,0422,0152,0262,025184.2
### `/fortunes` Fetch a table, add a row, sort, render HTML.
concurrency163264128256p99 ms
kinetis13,63114,09014,57715,15815,68933.3
slim-frankenphp13,82014,53015,04015,65816,04029.2
slim6,0726,7496,9557,1177,13475.5
kinetis-fpm4,9715,4055,4985,5435,501105.4
yii24,9255,3415,3845,5165,55891.1
symfony3,4943,5893,6963,6433,621120.6
codeigniter2,0372,0502,0592,0302,024186.9
cakephp2,8012,9122,8922,9162,862133.7
laravel-octane4,3084,4204,3924,3174,177162.7
laravel1,8741,9171,9241,9051,870173.9
### `/queries` N random rows per request, at concurrency 256.
queries15101520p99 ms
kinetis17,55511,6248,0085,7884,53862.3
slim-frankenphp17,81211,3087,0115,0223,90667.7
slim7,5026,3855,4834,7974,33994.8
kinetis-fpm5,3913,8842,9322,4152,042146.8
yii25,7894,7013,8653,2632,875129.2
symfony3,8233,4063,1012,8412,602127.0
codeigniter2,1442,0171,8271,7071,586246.4
cakephp2,9872,2821,9281,6071,409209.1
laravel-octane4,4203,0752,2421,7651,432352.8
laravel1,9841,6311,3731,1741,041283.2
### `/updates` N rows read and written per request, at concurrency 256.
queries15101520p99 ms
kinetis14,1516,0983,0001,9731,504347.9
slim-frankenphp13,7275,3992,8822,0081,533245.6
slim6,8644,9033,1752,3641,860200.2
kinetis-fpm4,8202,7891,9061,4231,130426.8
yii25,4453,7282,6622,0471,588245.9
symfony3,7522,7732,4741,8261,500368.2
codeigniter2,1231,8631,5651,3251,200376.3
cakephp2,9371,9971,4521,1611,002370.7
laravel-octane3,9482,2811,4311,079841435.2
laravel1,8841,3551,043830716799.0
## Same runtime, different framework `kinetis` and `slim-frankenphp` both run FrankenPHP in worker mode with 20 workers, so this pair isolates the framework itself from the runtime underneath it. Ratios above 1.05× or below 0.95× are highlighted; anything between sits inside this rig's run-to-run noise.
testlevelkinetis slim-frankenphpratio
/json1623,84824,3890.98×
/json3224,58525,2800.97×
/json6425,00525,5270.98×
/json12825,18125,7660.98×
/json25625,67626,3100.98×
/plaintext1623,76624,3780.97×
/plaintext3224,60725,2720.97×
/plaintext6424,64325,5380.96×
/plaintext12824,83525,8260.96×
/plaintext25625,88826,3030.98×
/db1616,07515,4101.04×
/db3216,65616,1701.03×
/db6416,94216,7681.01×
/db12817,80117,4151.02×
/db25618,73218,1401.03×
/fortunes1613,63113,8200.99×
/fortunes3214,09014,5300.97×
/fortunes6414,57715,0400.97×
/fortunes12815,15815,6580.97×
/fortunes25615,68916,0400.98×
/queries117,55517,8120.99×
/queries511,62411,3081.03×
/queries108,0087,0111.14×
/queries155,7885,0221.15×
/queries204,5383,9061.16×
/updates114,15113,7271.03×
/updates56,0985,3991.13×
/updates103,0002,8821.04×
/updates151,9732,0080.98×
/updates201,5041,5330.98×
The two track each other within a few percent until query fan-out grows: 0.99× at one query per request, 1.14× at ten, 1.16× at twenty. That gap is Kinetis issuing a request's queries concurrently — see {doc}`concurrency` — and it is the one place where the framework rather than the runtime shows up. The single 1.13× on `/updates` at five queries is an outlier the levels either side of it don't support; treat it as noise. ## Versions tested | Target | Package | Version | PHP | |---|---|---|---| | `kinetis` | kinetis/framework | 1.17.1 | 8.5.9 (FrankenPHP, ZTS) | | `kinetis-fpm` | kinetis/framework | 1.17.1 | 8.4.24 | | `slim-frankenphp` | slim/slim | 4.15.2 | 8.5.9 (FrankenPHP, ZTS) | | `slim` | slim/slim | 4.15.2 | 8.4.24 | | `yii2` | yiisoft/yii2 | 2.0.55 | 8.4.24 | | `symfony` | symfony/framework-bundle | 7.4.16 | 8.4.24 | | `cakephp` | cakephp/cakephp | 5.4.1 | 8.4.24 | | `codeigniter` | codeigniter4/framework | 4.7.4 | 8.4.24 | | `laravel` | laravel/framework | 13.25.0 | 8.4.24 | | `laravel-octane` | laravel/framework + laravel/octane | 13.25.0 + 2.19.0 | 8.5.9 (FrankenPHP, ZTS) | `kinetis`/`kinetis-fpm` also ran kinetis/persistence 1.10.1 and kinetis/query-builder 1.2.4. FrankenPHP-based targets run a newer PHP than the PHP-FPM targets because that's what the `dunglas/frankenphp` image ships — see {doc}`runtime-adapters` for how Kinetis picks a runtime. ## Reading these numbers **Worker count is a variable this run holds fixed.** Both worker-mode targets got 20 workers on 8 vCPUs. An earlier run on a 2-CPU machine with 2 workers each put `kinetis` further ahead of the control on `/queries` and `/updates`. Where workers are plentiful, a blocking framework gets its parallelism from workers instead, and the gap narrows — see {doc}`performance-tuning` for how to size a deployment. **`/updates` is bound by the database's own write path.** At twenty queries per request every target lands between 716 and 1,860 req/s, and `slim` wins it. Row locks, index maintenance and the redo log set that ceiling; the database instance runs at the same load for every target, so no application-side change moves that number. **One measurement each, not an average.** Targets ran back to back under identical conditions, but every cell is a single 15-second run after a 5-second warmup. Differences under roughly 5% are not distinguishable from noise. **Not comparable to published TechEmpower figures.** The methodology matches — separate application, database and client machines — but the hardware and configuration do not. ## Full methodology The [benchmark repository](https://github.com/aln-1/kinetis-benchmarks) has the complete picture: every framework's implementation, the AWS infrastructure as code, and the fairness rules applied identically across all ten targets — everything needed to run the same sweep yourself and check these numbers directly. ## See also - {doc}`performance-tuning` — the settings these numbers were produced with, and how to apply them to your own application. - {doc}`runtime-adapters` — worker thread sizing, the single setting that moved these results most. - {doc}`caching` — the production AOT cache the benchmarked configuration builds ahead of time. - {doc}`concurrency` — the reason the query-heavy tests scale the way they do.