Refreshing benchmarks/README.md against pinned PHP 8.2 produced the opposite of the expected result on most shapes. Measured on a quiet box, pinned container, 5 iterations averaged, outputs verified identical.
Micro-benchmarks — 3M iterations each
| construct |
Zend 8.2 |
native AOT |
ratio |
++$a in a for loop |
37 ms |
453 ms |
12.2× slower |
strlen('hallo') call |
39 ms |
434 ms |
11.1× slower |
| user function call |
69 ms |
936 ms |
13.6× slower |
$s = $s + $i * 2 - 1 |
62 ms |
273 ms |
4.4× slower |
It is not startup
An empty script: native 6 ms vs Zend 22 ms — the compiled binary starts ~3.7× faster despite being 14.8 MB. So the deficit is in the emitted code for basic operations, not process init. (I initially assumed startup and was wrong; the measurement corrected it.)
It is not uniformly slow
fibo(30) native runs 7.7× faster than Zend (0.0133 s vs 0.1019 s), and fibo(32) 7.6×. Note fibo_r is typed (int $n): int) — typed integer recursion lowers well. The 3M-iteration loops above are untyped, which is the common case in real PHP.
That gap between typed and untyped is the likely lead: an untyped ++$a or $s + $i * 2 appears to go through boxed-value runtime calls per operation, where Zend's VM has a tuned fast path. 3M increments in 453 ms is ~6.6 M ops/s, which is far below what native code should manage and below Zend's ~81 M ops/s.
Worth checking first whether the emitted IR for an untyped loop body contains calls into the value runtime rather than machine arithmetic (PHP_COMPILER_DUMP_IR=1), and whether type inference is reaching these locals at all.
Why this matters for release
The project's public speed claim rests on the AOT column. Today that column is: unmeasurable for 4 of 7 benchmarks (crash #23472 / wrong output #23471), 7.7× faster on typed recursion, and 15× slower on the loop/call-heavy simple benchmark (1.0131 s vs 0.0658 s).
Done when: untyped loop and call micro-benchmarks are at least at parity with Zend, and benchmarks/README.md carries the numbers with every column filled and output-verified.
Refreshing
benchmarks/README.mdagainst pinned PHP 8.2 produced the opposite of the expected result on most shapes. Measured on a quiet box, pinned container, 5 iterations averaged, outputs verified identical.Micro-benchmarks — 3M iterations each
++$ain a for loopstrlen('hallo')call$s = $s + $i * 2 - 1It is not startup
An empty script: native 6 ms vs Zend 22 ms — the compiled binary starts ~3.7× faster despite being 14.8 MB. So the deficit is in the emitted code for basic operations, not process init. (I initially assumed startup and was wrong; the measurement corrected it.)
It is not uniformly slow
fibo(30)native runs 7.7× faster than Zend (0.0133 s vs 0.1019 s), andfibo(32)7.6×. Notefibo_ris typed (int $n): int) — typed integer recursion lowers well. The 3M-iteration loops above are untyped, which is the common case in real PHP.That gap between typed and untyped is the likely lead: an untyped
++$aor$s + $i * 2appears to go through boxed-value runtime calls per operation, where Zend's VM has a tuned fast path. 3M increments in 453 ms is ~6.6 M ops/s, which is far below what native code should manage and below Zend's ~81 M ops/s.Worth checking first whether the emitted IR for an untyped loop body contains calls into the value runtime rather than machine arithmetic (
PHP_COMPILER_DUMP_IR=1), and whether type inference is reaching these locals at all.Why this matters for release
The project's public speed claim rests on the AOT column. Today that column is: unmeasurable for 4 of 7 benchmarks (crash #23472 / wrong output #23471), 7.7× faster on typed recursion, and 15× slower on the loop/call-heavy
simplebenchmark (1.0131 s vs 0.0658 s).Done when: untyped loop and call micro-benchmarks are at least at parity with Zend, and
benchmarks/README.mdcarries the numbers with every column filled and output-verified.