PHP 8.0 — JIT compiler: what it is and when it helps
Concept
PHP 8.0 introduced the JIT (Just-In-Time) compiler as part of OPcache. It compiles hot bytecode instructions directly to native machine code at runtime, bypassing the PHP VM's opcode interpreter for those paths.
How PHP normally executes: Source → Lexer/Parser → AST → Opcodes → OPcache (opcodes cached) → Zend VM interprets opcodes → CPU. The Zend VM is a dispatch loop in C that reads each opcode and calls the corresponding handler.
What JIT adds: For frequently executed code paths ("hot" paths), JIT compiles opcodes to native x86-64 (or ARM64) machine code. The next time that path runs, the CPU executes it directly — no VM dispatch overhead. JIT uses a call graph (call-based tracing, not method JIT) to find hot functions.
Types of JIT in PHP 8: opcache.jit = tracing compiles traces across function boundaries (most aggressive). opcache.jit = function compiles function-by-function (more conservative). opcache.jit = disable or 0 turns it off.
When JIT helps vs doesn't help: JIT most benefits CPU-bound code — mathematical computations, image processing, data serialization. It barely helps I/O-bound PHP web apps — if your request spends 80% of time waiting for the database, JIT doesn't speed up the database wait. Real-world benchmarks: web frameworks see 5-15% improvement; pure computation benchmarks see 2-5× improvement.
Configuration: Requires opcache.enable=1, opcache.jit_buffer_size=128M (or larger), opcache.jit=tracing. In Laravel Octane (long-running process), JIT warm-up is amortized across many requests.
Code Example
<?php
declare(strict_types=1);
// CPU-intensive benchmark — JIT shines here
function mandelbrot(float $cReal, float $cImag, int $maxIter = 100): int
{
$zReal = $zImag = 0.0;
for ($i = 0; $i < $maxIter; $i++) {
$zReal2 = $zReal * $zReal;
$zImag2 = $zImag * $zImag;
if ($zReal2 + $zImag2 > 4.0) return $i;
$zImag = 2.0 * $zReal * $zImag + $cImag;
$zReal = $zReal2 - $zImag2 + $cReal;
}
return $maxIter;
}
$start = hrtime(true);
$sum = 0;
for ($x = -2.0; $x <= 1.0; $x += 0.01) {
for ($y = -1.5; $y <= 1.5; $y += 0.01) {
$sum += mandelbrot($x, $y);
}
}
$ms = (hrtime(true) - $start) / 1_000_000;
echo "Mandelbrot: {$ms}ms (sum={$sum})\n";
// Without JIT: ~1200ms
// With JIT (tracing): ~300ms — 4× faster for pure computation
// Checking JIT status
if (function_exists('opcache_get_status')) {
$status = opcache_get_status();
if ($status && isset($status['jit'])) {
var_dump($status['jit']['enabled']); // bool
var_dump($status['jit']['buffer_free']); // free JIT buffer
}
}
// I/O-bound code — JIT barely helps
function ioHeavy(): void
{
// JIT can't speed up waiting for database/network/filesystem
$result = file_get_contents('https://example.com'); // I/O wait dominates
$data = json_decode($result); // this part might benefit slightly
}
// php.ini settings for JIT:
// opcache.enable=1
// opcache.enable_cli=1 (for CLI scripts)
// opcache.jit=tracing (or 1255 — a numeric form: tracing mode)
// opcache.jit_buffer_size=128M