Summary
Current default opcache.jit=function gives us ~42k req/s on the Mandelbrot benchmark (ZTS, 8w). A flamegraph from profile-hetzner.yml shows that ~70% of CPU in CPU-bound workloads is already in the JIT buffer (addresses 0x8000XXXX), with only ~20% in libphp (interpreter/runtime) and ~0.2% in our Rust code. That confirms JIT is firing — but function JIT compiles each hot function individually and cannot speculate across calls or inline.
opcache.jit=tracing records runtime type profiles and compiles hot traces across function boundaries, inlining and specialising. For tight inner loops (Mandelbrot is the canonical case) it typically wins 20–40% over function JIT.
Proposal
- Add an INI control (already wired via
overrides.jit_buffer_size) that also lets users pick jit=tracing|function|off, default tracing.
- Rebuild with
tracing and rerun the full benchmark matrix. Pay special attention to:
- Mandelbrot (pure CPU-bound): primary target, expected 20–40% gain.
- Raw Hello World: likely neutral (no hot loops), but measure for regressions.
- Laravel/Symfony worker mode: could gain 5–15% on request bootstrap.
- Binary 50KB: likely neutral.
- Compare Firefox Profiler call-trees before/after to verify more time in tighter native code (fewer 0x8000 sites but each hotter) and less in libphp helpers.
Risks
tracing has a higher warmup cost — first dozens of requests per route may be slower while traces are recorded. Our worker mode amortises this trivially.
- Buffer size: tracing typically consumes 2–4× more JIT memory than function mode. Current default
jit_buffer_size=64M should handle it but re-check on Laravel.
- Regressions on certain codepaths have been reported historically. Always run the full matrix.
Acceptance criteria
- Config exposes
php.opcache_jit (off|function|tracing), default tracing.
- Benchmark matrix rerun committed to
benchmarks/results/ showing Mandelbrot ZTS 8w ≥ 50k req/s.
- No workload regresses by >5% vs current numbers.
Related
Summary
Current default
opcache.jit=functiongives us ~42k req/s on the Mandelbrot benchmark (ZTS, 8w). A flamegraph fromprofile-hetzner.ymlshows that ~70% of CPU in CPU-bound workloads is already in the JIT buffer (addresses0x8000XXXX), with only ~20% in libphp (interpreter/runtime) and ~0.2% in our Rust code. That confirms JIT is firing — butfunctionJIT compiles each hot function individually and cannot speculate across calls or inline.opcache.jit=tracingrecords runtime type profiles and compiles hot traces across function boundaries, inlining and specialising. For tight inner loops (Mandelbrot is the canonical case) it typically wins 20–40% over function JIT.Proposal
overrides.jit_buffer_size) that also lets users pickjit=tracing|function|off, defaulttracing.tracingand rerun the full benchmark matrix. Pay special attention to:Risks
tracinghas a higher warmup cost — first dozens of requests per route may be slower while traces are recorded. Our worker mode amortises this trivially.jit_buffer_size=64Mshould handle it but re-check on Laravel.Acceptance criteria
php.opcache_jit(off|function|tracing), defaulttracing.benchmarks/results/showing Mandelbrot ZTS 8w ≥ 50k req/s.Related
turbine-profile-zts-php8.5artifact in run 24897714344.