Skip to content

Perf: generated code is 4-14x SLOWER than Zend on basic loops and calls #23483

Description

@PurHur

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions