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)
| Mode | Description | When to use |
|---|---|---|
off | JIT disabled, only OPcache bytecode caching. | Baseline, or if JIT causes stability issues. |
tracing | Compiles hot traces (frequently executed paths). | General‑purpose, best for mixed CPU/I/O workloads. |
function | Compiles 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 1orSLEEP()) - Workers: All environments configured with 20 parallel workers:
- FPM:
pm = static,pm.max_children = 20 - RoadRunner:
pool.num_workers: 20 - Swoole:
worker_num = 20
- FPM:
- JIT settings (except
offmode):opcache.jit = tracing|functionopcache.jit_buffer_size = 128Mopcache.enable_cli = 1(for RoadRunner/Swoole) - Business logic (each request):
- 1000 ×
sqrt($i)CPU loop - One PDO query (
SELECT 1for 0 ms,SELECT SLEEP(0.05)for 50 ms,SELECT SLEEP(0.2)for 200 ms)
- 1000 ×
- 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
| Environment | JIT off | JIT tracing | JIT function |
|---|---|---|---|
| PHP‑FPM | 3051 RPS (55ms) | 2979 RPS (55ms) | 2974 RPS (58ms) |
| RoadRunner | 2478 RPS (73ms) | 2591 RPS (73ms) | 2356 RPS (78ms) |
| Swoole | 4923 RPS (53ms) | 4981 RPS (62ms) | 4918 RPS (57ms) |

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
| Environment | 0 ms I/O | 50 ms I/O | 200 ms I/O |
|---|---|---|---|
| PHP‑FPM (tracing) | 2979 RPS | 388 RPS | 98 RPS |
| RoadRunner (tracing) | 2591 RPS | 2664 RPS | 2542 RPS |
| Swoole (tracing) | 4981 RPS | 5008 RPS | 5060 RPS |

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

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 type | Recommended environment | JIT setting |
|---|---|---|
| Traditional shared hosting / FPM | PHP‑FPM | opcache.jit = tracing (or leave default) |
| API with moderate I/O (database, Redis) | RoadRunner or Swoole | tracing + jit_buffer_size=128M |
| High‑throughput real‑time / WebSocket | Swoole | tracing + coroutine optimisations |
| Pure CPU number crunching | Swoole > FPM > RoadRunner | any 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:
- Set up a clean Ubuntu 22.04 server with PHP 8.5, Nginx, MySQL.
- Clone the repo and run
setup.sh(creates database, worker scripts, RoadRunner binary, Swoole server). - Execute
run_benchmarks.sh– it will automatically switch JIT modes, restart services, and run 3×60sabtests for each combination. - 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 mode –
tracingis 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.