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: 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 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 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 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 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 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 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

  • Performance tuning — the settings these numbers were produced with, and how to apply them to your own application.

  • Runtime Adapters — worker thread sizing, the single setting that moved these results most.

  • Caching & AOT Compilation — the production AOT cache the benchmarked configuration builds ahead of time.

  • Concurrency — the reason the query-heavy tests scale the way they do.