PHP 8.5 JIT Deep Tuning: tracing vs function vs off — Full Comparison (2026)

After our initial JIT on/off benchmark and the I/O‑sensitive workload analysis, many readers asked two specific questions: Which JIT mode (tracing vs function) delivers the best real‑world performance? and Does the JIT behaviour differ between traditional PHP‑FPM, long‑running application servers like RoadRunner, and event‑driven servers like Swoole?

This article answers exactly those questions. This PHP 8.5 JIT tracing vs function benchmark reveals that mode differences are often overshadowed by environment choices. We ran 13 benchmark tasks across three environments (FPM, RoadRunner, Swoole) and three JIT modes (off, tracing, function), with additional I/O delay levels (0 ms, 50 ms, 200 ms). All tests used 20 parallel workers and identical business logic (1000 sqrt iterations + a SELECT 1 query). Below you’ll find hard numbers, surprising discoveries, and actionable recommendations.


🧠 JIT Modes Explained (Briefly)

ModeDescriptionWhen to use
offJIT disabled, only OPcache bytecode caching.Baseline, or if JIT causes stability issues.
tracingCompiles hot traces (frequently executed paths).General‑purpose, best for mixed CPU/I/O workloads.
functionCompiles whole functions when they become hot.CPU‑heavy functions that are called many times.

In PHP 8.5 both modes can be enabled via opcache.jit=tracing or opcache.jit=function, with a recommended buffer of 128M for JIT traces.


🧪 Test Environment

For comparison, see our previous application server benchmark.

  • Server: 4 vCPU, 8GB RAM, Ubuntu 22.04
  • PHP: 8.5.6 (compiled with OPcache and JIT support)
  • Database: MySQL 8.4 on same host (bench_db with SELECT 1 or SLEEP())
  • Workers: All environments configured with 20 parallel workers:
    • FPM: pm = static, pm.max_children = 20
    • RoadRunner: pool.num_workers: 20
    • Swoole: worker_num = 20

  • JIT settings (except off mode):
    opcache.jit = tracing|function
    opcache.jit_buffer_size = 128M
    opcache.enable_cli = 1 (for RoadRunner/Swoole)
  • Business logic (each request):
    • 1000 × sqrt($i) CPU loop
    • One PDO query (SELECT 1 for 0 ms, SELECT SLEEP(0.05) for 50 ms, SELECT SLEEP(0.2) for 200 ms)

  • Load generator: ab -c 100 -t 60 (ApacheBench, 100 concurrent connections, 60 seconds), 3 runs per configuration.

We excluded FrankenPHP (JIT incompatible with ZTS) and Swoole’s earlier instability (resolved by using PHP 8.5 + Swoole 5.x).


📊 Benchmark Results

All values are average requests per second (RPS) over three runs; P99 latency in milliseconds.

1. CPU‑intensive (0 ms I/O) – JIT mode comparison

EnvironmentJIT offJIT tracingJIT function
PHP‑FPM3051 RPS (55ms)2979 RPS (55ms)2974 RPS (58ms)
RoadRunner2478 RPS (73ms)2591 RPS (73ms)2356 RPS (78ms)
Swoole4923 RPS (53ms)4981 RPS (62ms)4918 RPS (57ms)
PHP 8.5 JIT mode comparison: off vs tracing vs function across FPM, RoadRunner and Swoole
Figure 1: JIT mode comparison – CPU‑intensive workload (0ms I/O). Swoole leads, JIT mode differences are minimal.

Key insight: JIT mode has minimal impact (<5% difference) in pure CPU scenarios. Environment choice dominates: Swoole is ~1.6× faster than FPM, and FPM is ~1.2× faster than RoadRunner for this synthetic CPU loop.

2. I/O‑intensive (50ms and 200ms database sleep) – tracing mode only

Environment0 ms I/O50 ms I/O200 ms I/O
PHP‑FPM (tracing)2979 RPS388 RPS98 RPS
RoadRunner (tracing)2591 RPS2664 RPS2542 RPS
Swoole (tracing)4981 RPS5008 RPS5060 RPS
Bar chart showing RPS for FPM, RoadRunner, Swoole at 0ms, 50ms, 200ms I/O delay
Figure 2: Impact of I/O delay – tracing JIT. FPM collapses, while RoadRunner and Swoole maintain high throughput.

Key insight: FPM collapses under I/O blocking (from ~3000 RPS to ~100 RPS at 200ms). RoadRunner and Swoole maintain high throughput thanks to non‑blocking I/O and coroutines. Swoole even slightly outperforms its own 0ms result at 200ms due to better CPU cache locality (noise margin).


🔍 Analysis

Line chart: RPS versus I/O delay, showing FPM dropping sharply while RoadRunner and Swoole stay flat
Figure 3: RPS degradation as I/O delay increases. FPM is heavily affected; RoadRunner and Swoole are resilient.

Why does JIT mode matter so little in these tests?

Our CPU loop (sqrt 1000 times) is extremely regular and short. Both tracing and function can optimise it well. Real‑world applications with larger, varied code paths may see larger differences. For most PHP apps, the environment (FPM vs Swoole vs RoadRunner) and I/O pattern matter far more than the JIT tuning.

Why is RoadRunner slower than FPM on pure CPU?

RoadRunner’s PSR‑7 worker loop adds a small overhead (request/response serialisation). On CPU‑bound tasks, this overhead is noticeable. On I/O‑bound tasks, it becomes negligible because the bottleneck shifts to network/database.

Why does Swoole dominate all scenarios?

Swoole’s event loop and coroutine scheduler are extremely lightweight. It also reuses the same worker process for many requests, eliminating process startup overhead. Combined with JIT, it achieves the highest RPS across the board.


🎯 Recommendations

Your application typeRecommended environmentJIT setting
Traditional shared hosting / FPMPHP‑FPMopcache.jit = tracing (or leave default)
API with moderate I/O (database, Redis)RoadRunner or Swooletracing + jit_buffer_size=128M
High‑throughput real‑time / WebSocketSwooletracing + coroutine optimisations
Pure CPU number crunchingSwoole > FPM > RoadRunnerany JIT mode (difference <5%)

Concrete php.ini snippet (for CLI / RoadRunner / Swoole)

opcache.enable = 1
opcache.enable_cli = 1
opcache.jit = tracing
opcache.jit_buffer_size = 128M
; For FPM, enable_cli is not needed.

Switching JIT modes on the fly (for testing)

We provide a small shell script to switch modes for both FPM and CLI:

#!/bin/bash
# switch_jit.sh [off|tracing|function] [fpm|cli|both]
# ... (full script available in the reproducibility repository)

📁 Reproducibility

All test scripts, configuration files, and raw ab outputs are available in the GitHub repository. To replicate:

  1. Set up a clean Ubuntu 22.04 server with PHP 8.5, Nginx, MySQL.
  2. Clone the repo and run setup.sh (creates database, worker scripts, RoadRunner binary, Swoole server).
  3. Execute run_benchmarks.sh – it will automatically switch JIT modes, restart services, and run 3×60s ab tests for each combination.
  4. Collect results with extract_results.sh.

We also documented the exact steps to install Swoole 5.x for PHP 8.5 (important: use pecl install swoole and enable opcache.enable_cli=1).

Interested in real‑world framework performance? Check out PHP 8.5 JIT in WordPress, Laravel & Symfony.


🧵 Final Words

  • Don’t obsess over JIT modetracing is a safe, almost‑always‑best choice.
  • If you are still on FPM and your app does significant I/O, consider migrating to RoadRunner or Swoole. The performance gain (especially under load) is 5–20×.
  • Swoole is the performance king but requires code adjustments (coroutines). RoadRunner offers a smoother transition for existing PSR‑15 applications.

We hope this data helps you make informed decisions. As always, benchmark your own real workload – synthetic tests give direction, but production behaviour is final.This PHP 8.5 JIT tracing vs function benchmark shows that while JIT mode matters less than environment, tracing remains the safest default.


Have you tested JIT with your own application? Share your results in the comments!

Published on June 6, 2026 – PHP 8.5 JIT tuning benchmark.

Leave a Comment