Measured · 21–23 August 2026 · detailed version · Türkçe (kısa sürüm)

Ten Languages,
One API


Same API. Ten languages. Three sizes. Thirty builds. All pass all tests. Then count tokens. Then measure cold start, latency, throughput, CPU and RAM at 100, 1,000 and 10,000 requests.

This is the long version: every table, every tier, and every mistake we made and fixed. Short version at docs/benchmark.html.

30builds
259tests each
90perf runs
29,973source lines
2.80×token spread
40×speed spread

1. Three sizes

Tierroutestests What it add
small722 CRUD, validation, derived field, two-key sort, aggregate with rounded mean and nullable field
mid1698 session auth, roles, row-level access, middleware chain, structured JSON log, error envelope with machine codes and request id, multi-error validation, paging, whitelisted sort, status state machine, cascade delete, conflicts
large34139 forced 4-module split, ETag + If-Match concurrency, idempotency keys with replay, soft delete and restore, audit trail, outbox, per-session quota, bulk write with per-item result, search, grouped reports, metrics by route pattern, comments, user admin, admin-only field

Large tier force a module split: domain, store, service, api, each import only from above. That is real enterprise shape, and it is where a language with no module pay twice — C and C++ must write a header per module. C three header alone = 368 of its 3,245 line.

2. How we verify

Nothing count until it work. Two gate, both must pass.

Gate 1 — conformance

Real HTTP against a live server on port 8080. No mock. Check status code, exact top-level key set, every value, list order, validation-detail order, and headers X-Request-Id, ETag, X-Quota-Remaining, Idempotency-Replayed. Float compare with 1e-9 tolerance, everything else exact.

Gate 2 — log check

Mid and large only. A tool read server stdout and check every log line: one JSON object per line, exact key order, level match status class, no query string in path, right type each field. Spec also say nothing but log go to stdout, so each build must silence its own framework.

Order of work each tier: spec first, test second, then one Python reference driven to a full pass, then the other nine. Every build re-verified by hand, separate from the report it came with. Result: 30/30 pass 22 / 98 / 139, and all 20 mid and large build pass the log check.

3. Our three mistakes

Three languages looked bad because of us, not because of them.

All three found by asking why a number looked worse than it should. All three fixed before publish. Old data kept for audit in perf-results-before-*.json.

Mistake 1 — PHP had OPcache off

php -S run the CLI SAPI, where opcache.enable_cli default Off. So PHP recompile the whole app from source every request. No production PHP run that way.

Mistake 2 — C and C++ had no keep-alive

Both send Connection: close every response, so a new TCP handshake per request. HTTP/1.1 default is a persistent connection. Zig also hand-write raw socket with no framework, and Zig do keep-alive right — that is what prove this our defect, not a property of C.

Small tier, 10,000 requestbefore afterchange
C p5011.587 ms 0.166 ms70× faster
C req/s96 3,45436× more
C++ p5011.164 ms 0.200 ms56× faster
C++ req/s99 2,56126× more

Before the fix we ranked C 8th on speed. C is actually near the top. The fix also add safety: cap 1,000 request per connection, SO_RCVTIMEO 5 s so one idle client cannot deadlock a single-thread accept loop, and TCP_NODELAY in C because it write header and body as two send and Nagle would stall the second. Correct keep-alive cost C about +26% token. Protocol-correct code is more code. Every number here include that.

Mistake 3 — our PHP store was O(n) per request

Covered in full in the next section. Short version: the large tier grow an audit log, and our store re-serialize the whole dataset every request, so PHP paid O(total state) per request for what every other language do as an O(1) append to a list in RAM.

What we checked and left alone

Everything else reviewed for the same kind of handicap and found fair: all ten run a single process with default framework setting, the compiled language all build in standard release mode (-O2, --release, ReleaseSafe), and no language get a tuning flag another is denied.

4. Where PHP time really go

PHP looked far worse than any of us expected, so we took it apart layer by layer. Same server, fresh store, GET only, warm. Three separate things were wrong, and all three were ours.

Layer by layer, after the OPcache fix

Layerp50this layer add
bare PHP, no framework0.874 ms —
+ Slim construct, no store, no logic 5.113 ms+4.24 ms
+ our small-tier store and routes5.384 ms +0.27 ms
+ our large-tier store and routes6.410 ms +1.30 ms

Bare PHP 0.874 ms — faster than C# and faster than Python. About 94% of the gap was Slim container, router and middleware stack built from scratch on every request. That is what shared-nothing require. Every other language here build its router one time at startup and keep it for the life of the process.

So we threw the framework away

In Go or Rust the framework is the floor. In PHP it is a choice.

You cannot serve HTTP in Go with less than net/http. PHP is a request handler by design, and one front controller on $_SERVER['REQUEST_URI'] is ordinary production PHP. Measuring Slim charge PHP for a decision many PHP developer never make.

So PHP here carry zero dependency at all three tier — no composer, no vendor, no autoloader, just public/index.php and its own file. Same store, same identifier name, same logic. Pass the same 22 / 98 / 139 test. Only the HTTP layer differ from the old framework build.

We measured both way before we dropped the framework one, so the framework cost below is a measured number, not a guess. The old run stay in perf-variance/removed-slim-run*.json.

Mistake 3: the store was O(n). Measured.

Store operationsmall store 501 B big store 144 KB
file_get_contents0.049 ms0.039 ms
file_put_contents0.327 ms0.741 ms
json_decode0.016 ms8.581 ms
full read + decode + encode + write0.641 ms11.519 ms

File I/O was never the problem. JSON serialization was. With a fresh store, PHP answer GET /health at the large tier in 1.656 ms — healthy, same range as Python. After a 10,000-request run it had degraded to 25.2 ms, because the audit trail and outbox grow and our store rewrote the whole dataset each time.

Fix: audit and outbox move to append-only JSON Lines file written with FILE_APPEND and never rewritten; auditCount, outboxCount and outboxDelivered counter in store.json so /stats and /metrics stay O(1); and an outboxFlushedThrough watermark so flush stay O(1) and each event delivered flag is derived on read rather than rewritten.

2,000 status change on one task, so only audit grow p50 startp50 enddrift
before fix7.783 ms18.402 ms2.36×
after fix7.523 ms7.547 ms1.00×

Flat. Large tier p50 drop 25.2 → 8.6 ms. Cost: +1,216 token, because O(1) persistence is more code than O(n). That move PHP from 4th to 5th on large-tier token. Honest trade, and every number here include it.

A mixed create-and-delete workload still drift after the fix, but that residue is not the audit trail: it soft-delete 1,000 task, and those row stay in store.json as live domain state that every build in the tier also hold. That part is legitimate and stay.

Framework cost, language and logic held constant

10,000 requestno framework with Slim 4gain
small tier p501.362 ms 6.102 ms4.5×
mid tier p501.452 ms 6.306 ms4.3×
large tier p508.636 ms 17.588 ms2.0×
small CPU ms/req0.736 4.7706.5×
mid CPU ms/req0.901 5.0125.6×
large CPU ms/req5.097 9.3551.8×
large token15,607 16,2381.04×

No framework also cost fewer token: 15,607 against 16,238 at the large tier, plus zero manifest because there is no composer file. So Slim cost about 4.2 ms per request, roughly 4% more token, a manifest and a vendor tree — and nothing measured here got better for it. That is why PHP is published with no framework.

PHP number are median of three run — and it hardly needed the median.

PHP repeat tight: small tier gave 1.362, 1.361, 1.376 ms, mid gave 1.452, 1.457, 1.448 ms, large gave 8.418, 8.636, 8.779 ms. Spread 1.1%, 0.6%, 4.3%. One of the most reproducible build in the whole benchmark, because a millisecond of real work swamp a 15.6 ms timer artifact that a sub-millisecond server cannot absorb.

Worth saying because the old framework build swung 13.73, 6.07, 6.10 ms at small and 14.12, 6.31, 6.28 ms at mid: its Composer autoloader touch about a hundred file per request and page-cache state decide the number. So the instability belonged to the framework, never the language. perf-variance/ hold every raw repeat, old build included.

Honest summary. PHP is not a slow language. Three separate handicap were stacked on it and all three were ours. What remain after removing them is the shared-nothing execution model itself: PHP cannot hold state between request, and this benchmark forbid a database, so PHP must serialize something on every request while the other nine keep everything in RAM for free. A persistent runtime — Swoole, RoadRunner, FrankenPHP — remove that too. Not tested here.

5. Token gap shrink when app grow

Bar = cost versus the cheapest build that tier. Right-hand number = change in ratio small to large; minus mean the gap closed.

Python S1.00× M1.00× L1.00× —
TypeScript S1.28× M1.13× L1.12× −0.16
C# S1.12× M1.08× L1.15× +0.03
Java S1.51× M1.35× L1.38× −0.13
PHP S1.94× M1.45× L1.48× −0.46
Go S1.75× M1.75× L1.56× −0.19
Rust S1.66× M1.72× L1.66× 0.00
Zig S2.08× M1.87× L1.84× −0.24
C++ S4.24× M2.50× L2.01× −2.23
C S5.05× M3.43× L2.80× −2.25
small, 7 routes mid, 16 routes large, 34 routes bar length relative to C at small tier
Low-level penalty is a fixed cost, not a multiplier.

C, C++ and Zig write an HTTP parser, a JSON parser, a JSON writer and a socket loop one time. Python and C# import all of it. Python start way ahead then gain nothing more. Every build pay about the same for route 35.

Gap worst-to-best: 5.05× at 7 routes → 3.43× at 16 → 2.80× at 34. C++ shed 2.23×, C shed 2.25×.

Measure on a toy app and you punish C by near double what a real app would show.

6. Token tables, all three tiers

Token from tiktoken o200k_base. A second encoder, cl100k_base, give the same order at every tier — that is the evidence the ranking belong to the source, not the encoder. Code line exclude blank and comment line.

Click any header to sort.

small tier — 7 routes

LanguageFrameworklines codecharstokens ratiocl100k
PythonFastAPI1381013,8339861.00×982
C#Minimal API1341114,3161,1001.12×1,082
TypeScriptExpress1521304,5751,2591.28×1,259
JavaSpring Boot1721507,0071,4891.51×1,405
RustAxum2111926,4151,6371.66×1,631
Gonet/http2632366,1541,7221.75×1,672
PHPno framework2472197,1661,9111.94×1,895
Zigstd.http2111867,5962,0472.08×2,038
C++raw winsock45641216,1584,1794.24×4,178
Craw winsock59354518,4034,9815.05×4,962

mid tier — 16 routes

Languagelinescode charstokensratio cl100k
Python47236915,2183,7611.00×3,751
C#45139716,5994,0541.08×3,963
TypeScript51345816,3624,2501.13×4,216
Java55049023,6525,0941.35×4,842
PHP62555520,4505,4641.45×5,433
Rust83377825,3726,4841.72×6,458
Go94687723,4006,5891.75×6,525
Zig69263626,1207,0221.87×6,986
C++1,04494737,3919,3852.50×9,341
C1,5321,40747,96112,9093.43×12,832

large tier — 34 routes

Languagelinescode charstokensratio cl100k
Python1,32197644,79110,5581.00×10,524
TypeScript1,4931,29647,78211,8741.12×11,798
C#1,3601,15653,57412,1801.15×12,016
Java1,6011,39469,17414,5211.38×13,977
PHP1,7711,47858,59115,6071.48×15,574
Go2,3382,09960,21716,5181.56×16,456
Rust2,1471,95169,34817,5601.66×17,534
Zig1,9951,78774,49819,4181.84×19,330
C++2,4672,18585,80021,2542.01×21,155
C3,2452,892109,02529,5322.80×29,321

7. Growth, small to large

Same data the other way round. This is the mechanism behind the shrink: the cheap build had the most room to grow into. Growth here is not good or bad, so nothing is colour-coded. A low multiple mean the build already carried a big fixed baseline at the small tier — C++ grow only 5.09× exactly because it start at 4,179 token for seven routes.

Click any header to sort.

Languagesmallmid largemid growth large growth
C#1,1004,05412,1803.69×11.07×
Rust1,6376,48417,5603.96×10.73×
Python9863,76110,5583.81×10.71×
Java1,4895,09414,5213.42×9.75×
Go1,7226,58916,5183.83×9.59×
Zig2,0477,02219,4183.43×9.49×
TypeScript1,2594,25011,8743.38×9.43×
PHP1,9115,46415,6072.86×8.17×
C4,98112,90929,5322.59×5.93×
C++4,1799,38521,2542.25×5.09×

8. Cold start

Millisecond from process spawn to the first good GET /health, median across the three tiers. The harness measure its own detection cost on every run, so the startup column have that floor removed instead of hiding it.

Click any header to sort.

Languagesmallmid largeprobe floor startup
C63815918.547 ms
Go90687313.655 ms
Zig6070591.857 ms
C++58727513.458 ms
Rust7171722.469 ms
PHP1671801822.8166 ms
TypeScript4634524653.3452 ms
C#96272245510.5712 ms
Python8681,2301,02714.81,012 ms
Java6,2185,2944,97619.45,275 ms

Java take 112× longer than C to accept its first request. For a long-lived server that mean nothing. For anything that scale to zero, restart often, or run in a serverless container, it is the number that decide.

9. Latency and throughput, all three tiers

10,000 request, sequential, one at a time. Read p50. p99 and max cluster on the 15.6 ms Windows timer tick — that is the client losing its scheduling quantum, not the server. Do not rank on req/s: it derive from the mean, and for a sub-millisecond server one stall cost the wall clock of ~50 normal request, so three repeat run of Go gave 580, 1,095 and 1,145 while its p50 barely moved. Full variance study in METHODOLOGY.md.

Click any header to sort.

small tier

Languagep50p90 meanp99req/s
C0.1660.2340.2790.73,454
C++0.2000.2650.3789.82,561
Zig0.2110.2760.3783.12,562
Rust0.2510.3200.52212.41,868
Go0.2920.4491.21633.4812
C#0.4040.6751.45734.3679
TypeScript0.4260.5890.96917.51,014
Java0.5831.1991.43918.7687
Python1.2563.1903.32149.6299
PHP1.36222.1026.50655.2153

mid tier

Languagep50p90 meanp99req/s
Zig0.1920.2570.31510.83,065
C++0.2140.2870.39111.32,481
C0.2160.2870.42311.72,280
Rust0.2640.3310.43411.52,240
Go0.2770.3560.4639.92,103
C#0.3800.5130.63110.51,551
TypeScript0.4280.5710.77313.41,269
Java0.5681.0491.17616.3838
Python1.3972.1702.23213.0445
PHP1.45222.8836.82558.8146

large tier

Languagep50p90 meanp99req/s
C++0.2180.2800.37911.22,543
C0.2400.3060.46611.62,084
Zig0.2540.3170.48712.01,986
Go0.3110.3940.51411.51,888
Rust0.3270.4030.69016.31,412
TypeScript0.4410.6180.81714.61,198
C#0.4440.6451.15718.7850
Java0.5951.1411.29616.8760
Python1.6214.3603.74350.4266
PHP8.63637.01416.10570.062

10. CPU and memory, all three tiers

10,000 request. Memory = resident set of the whole process tree, sampled every 20 ms. CPU = process-tree CPU second divided by request count, reported only from run of 1,000 or more, because the Windows CPU counter have about 15.6 ms resolution and a short run round to zero.

Click any header to sort.

small tier

LanguageCPU ms/reqidle RSS peak RSS
C0.0284.2 MB4.3 MB
C++0.0454.2 MB4.3 MB
Zig0.0533.1 MB3.2 MB
Rust0.0975.8 MB6.0 MB
Go0.1698.3 MB15.2 MB
TypeScript0.29765.2 MB71.5 MB
C#0.70147.9 MB64.8 MB
PHP0.73633.5 MB34.2 MB
Python1.25448.3 MB48.5 MB
Java1.692181.1 MB210.3 MB

mid tier

LanguageCPU ms/reqidle RSS peak RSS
Zig0.0533.3 MB3.4 MB
C0.0534.2 MB4.3 MB
C++0.0755.1 MB5.2 MB
Rust0.1056.0 MB6.3 MB
Go0.1868.4 MB15.5 MB
TypeScript0.29766.1 MB74.3 MB
C#0.49048.5 MB63.9 MB
PHP0.90134.3 MB34.7 MB
Python1.45549.2 MB50.1 MB
Java1.478185.5 MB204.6 MB

large tier

LanguageCPU ms/reqidle RSS peak RSSRAM vs Zig
C0.0594.2 MB4.7 MB1.3×
Zig0.0693.3 MB4.2 MB1.0×
C++0.0705.2 MB6.0 MB1.6×
Rust0.1506.2 MB7.5 MB1.9×
Go0.2308.6 MB16.2 MB2.6×
TypeScript0.35667.0 MB80.4 MB20.3×
C#0.65350.9 MB66.6 MB15.4×
Java1.613191.0 MB204.1 MB57.9×
Python1.76450.0 MB52.4 MB15.2×
PHP5.09734.5 MB36.7 MB10.5×

11. Does latency hold under load?

p50 at each request count, large tier. Below 1.0 mean it got faster while running. Above 1.0 mean it degrade. This is where a warm-up effect or an accumulating-state problem show itself.

Click any header to sort.

Language1001,000 10,000driftReading
Java1.3041.1820.5950.46×JIT warm up, 2.2× faster by the end
C#0.4990.5480.4440.89×tiered JIT, same effect, smaller
C++0.2290.2460.2180.95×flat
TypeScript0.4580.4640.4410.96×flat
C0.2470.2310.2400.97×flat
Rust0.3320.3360.3270.99×flat
Python1.5981.4641.6211.01×flat
Go0.2940.2930.3111.06×flat
Zig0.2380.2690.2541.06×flat
PHP2.3602.9268.6363.66×state accumulate on disk between request

PHP is the only real degradation, and after the append-only store fix the cause is no longer the audit trail — an isolated test of 2,000 audit-only write is flat at 1.00×. What remain is 1,000 soft-deleted task row living in store.json as domain state that every build in the tier also hold; PHP is just the only one that has to re-serialize it, because a shared-nothing process cannot keep it in memory. The absolute number improved a lot with the fix: 25.2 → 8.6 ms.

12. Token order is close to reverse of speed order

Cheap token, slow code. Expensive token, fast code.

C and C++ cost the most token — and take place 1 and 2 on speed. Python cost the fewest token — and place 9. The two list are near mirror image. Nothing sit good at both end.

Click any header to sort.

Languagetoken ratiotoken rank p50 msspeed rankshift Caveman say
C++2.01×100.2181+9fastest, second most token
C2.80×110.2402+9least CPU, fastest start, most token
Zig1.84×90.2543+6least RAM at 3.3 MB, zero dependency
Go1.56×70.3114+3good all round, most line of managed
Rust1.66×80.3275+3fast, safe, many token
TypeScript1.12×20.4416−4best mix, eat 67 MB
C#1.15×30.4447−4dense code, mid speed
Java1.38×40.5958−4JIT help later, 5 s start, 191 MB
PHP1.48×58.63610−5zero dependency, but must re-serialize state
Python1.00×11.6219−8fewest token, slow

Shift = token rank minus speed rank. Plus mean faster than the token cost suggest.

13. Findings

The abstraction cliff is a step, not a slope

At 7 routes C look terrible, 5.05×. At 34 routes it is 2.80×. Scaffolding written once then spread over every feature after. Most actionable result here: size a real backend from a toy comparison and you over-punish low-level by near double.

C use least CPU of all ten

0.059 CPU ms per request on the big app. 86× less than PHP, 27× less than Java. Also the fastest cold start, 47 ms. And it cost the most token of anything measured.

Zig use least RAM

3.3 MB idle against Java 191 MB — 57×. Standard library only, zero dependency, all three tier. Cost: 1.84× Python token, and Zig 0.16 delete std.net so almost no example exist for it.

PHP is not slow. We made it slow three times.

OPcache off, framework rebuilt per request, store O(n). With a fresh store PHP answer in 1.66 ms, and with nothing at all in 0.874 ms — faster than C# or Python. What remain is the shared-nothing model, not the language.

Framework cost PHP about 4.2 ms and save nothing

No framework is faster and smaller at every tier: 4.5×, 4.3× and 2.0× faster p50, 5× less CPU, 15,607 token against 16,238, and no manifest. We measured PHP both way before dropping the framework one, which make this a direct read on framework cost with language and logic held constant.

Java JIT worth 2.2×

p50 1.304 → 0.595 ms across the run, drift 0.46×, the only big improver. The bill come at start: 5,275 ms cold start, 191 MB RAM. Deployment shape decide whether that matter at all.

Go overtake Rust on token at large

1.56× against 1.66×, after losing at mid. Go overhead is per-declaration, and the forced 4-module split gave it somewhere to spread that. Go still write the most line of any managed language, 2,338.

C# dense, not bloated

Beat Python on line count at small and mid — 134 vs 138, 451 vs 472. Then lose second place to TypeScript at large and become the fastest-growing build, 11.07×. A module split tax static typing at every boundary.

Token track character, not line

Java write 550 mid line against Rust 833 and still cost fewer token, because Rust line are narrow. Go write fewer character than Java, 23,400 against 23,652, yet cost 1,495 more token: Go encode 3.6 char per token against Java 4.6.

Runtime spread crush token spread

Token 2.80×. Cold start 112×. CPU 86×. RAM 57×. Throughput 41×. Speed 40×. Source size is the flattest axis in the whole study.

14. Data quality

Every departure from a clean run, said out loud instead of smoothed over.

  1. C return 748 conflict at large tier, 10,000 request. C use fixed array and MAX_AUDIT is 2,048. The cycle write three audit entry, so the table fill after about 680 cycle and further write answer 409. Count was 8,781 200, 477 201, 748 409. So C latency there look slightly better than true, because a rejected write is cheap. Also a real finding: C is the only build with a hard ceiling on accumulated state.
  2. PHP number are median of three run. PHP repeat tight — 1.362 / 1.361 / 1.376 ms at small, inside 1.1% — so the median barely matter. The dropped framework build swung 13.73 / 6.07 / 6.10 ms, because its Composer autoloader touch about a hundred file per request. Raw repeat in perf-variance/.
  3. Do not rank on req/s. Three repeat run of Go gave 580, 1,095 and 1,145 req/s while p50 moved about 13%. Throughput derive from the mean, and for a sub-millisecond server one 15.6 ms stall cost the wall clock of ~50 request. The throughput order among C, C++, Zig, Go and Rust is not established by this data.
  4. p99 and max are host artifact. They cluster on multiple of 15.6 ms, the Windows timer tick, and jump up to fortyfold from p90. p50 and p90 are stable.
  5. Cold start had a 500 ms floor, now removed. The first probe used an HTTP client, and on Windows a refused loopback connection take hundreds of ms to report, which floored every cold start near 500 ms and made all the fast binary look identical. A raw socket probe replaced it, and the harness now measure its own detection cost every run.
  6. Peak RSS read 0.0 MB once. A fast build finish 100 request inside one 20 ms sampling interval after the peak was zeroed. Peak now seeded from a direct reading.
  7. CPU at 100 request unusable and not reported. The Windows process CPU counter have about 15.6 ms resolution so a short phase round to zero.
  8. The perf harness once hung instead of failing. The old readiness probe left its connection open, and the Zig single-thread server kept serving that dead socket and never accepted the real client. Worse, a failed request record no sample, so the loop while samples < target spun forever. The probe now close, and the loop raise on an error ceiling or on any cycle producing zero sample.
  9. One machine, one host, sequential client. No repetition across reboot, no process pinning, no tuned timer resolution. Sound for relative comparison, not for absolute capacity.

15. Where the thirty builds differ

No test case reach any of these. Recorded because each move the token count a little, or because a language force the choice. Full list in METHODOLOGY.md.

16. Limits — read before you quote a number

  1. Framework included, so this measure ecosystem too. Each language use what a real team would pick — which for PHP mean no framework, precisely because there the framework is optional and cost 4.2 ms. FastAPI and Minimal API hide an HTTP parser, JSON parser, JSON writer and socket loop that C, C++ and Zig write by hand. A real gap, real for a team, but not a measure of pure syntax. A stdlib-only run would squeeze the band hard and is not done here.
  2. Three thing left out on purpose. No password hash, no signed token, no wall-clock timestamp in a body. Each keep the work identical across the languages. Each is one library call in nine of them and a hundred-line detour in C — so leaving them out make the reported ratio kinder to C, not harsher.
  3. Count read cost, not write cost. This count token to express working code once. It do not count the retries a compiler would have caught, which is exactly where strong typing pay its token back.
  4. Tokenizer is a proxy. tiktoken o200k_base and cl100k_base, neither is Claude tokenizer, and no offline Claude tokenizer was available. Absolute count will move elsewhere; the order hold across both encoder, which is why the ratio column is the one to quote.
  5. One workload shape. JSON over HTTP, CRUD, in-memory state. It say nothing about numeric work, streaming, heavy concurrency, or a real database. And note the in-memory-state assumption is exactly what penalise PHP.
  6. Author effect. The same author wrote or directed all thirty, so style is more consistent than thirty independent team would be — that likely reduce variance versus the real world. The other way, per-language idiom expertise is uneven, and a specialist might write any one of these tighter.
  7. Three size = three dot. The shrink is large and consistent, but three dot do not fix the shape of a curve.
  8. No concurrency test. C, C++, Zig and the PHP built-in server are single-thread by design, so a concurrent benchmark would rank thread model rather than language. A separate study.