How PHP 8 JIT Compiler Works
This article provides a clear and direct explanation of the Just-In-Time (JIT) compiler introduced in PHP 8. You will learn how the JIT compiler integrates with the Zend VM, how it translates PHP bytecode into native machine code at runtime, and how it impacts the performance of different types of PHP applications.
The Traditional PHP Execution Flow
To understand the JIT compiler, it helps to understand how PHP executed code before PHP 8. PHP is an interpreted language. When a script runs, it goes through a multi-step process:
- Lexing and Parsing: The PHP engine reads the human-readable PHP code and converts it into an Abstract Syntax Tree (AST).
- Compilation: The engine compiles the AST into Zend opcodes (low-level instructions that the Zend Virtual Machine understands).
- Execution: The Zend Virtual Machine (VM) interprets these opcodes and translates them into machine code that the CPU can execute.
To speed this up, PHP uses OPcache. OPcache stores the compiled opcodes in shared memory so that PHP does not have to parse and compile the script on every single web request. However, the Zend VM still has to interpret those cached opcodes every time.
How the JIT Compiler Fits In
The JIT compiler in PHP 8 is implemented as an extension of OPcache. Instead of relying solely on the Zend VM to interpret opcodes, the JIT compiler compiles specific opcodes directly into CPU-specific machine code at runtime.
When the JIT-compiled code needs to run, the CPU executes it directly, completely bypassing the Zend VM interpreter. This reduces the overhead of instruction decoding and execution within the VM.
Tracing JIT vs. Function JIT
PHP 8 offers two main compilation strategies for JIT, which control how the engine decides what code to compile:
1. Function JIT
This approach compiles entire functions into machine code. While straightforward, it is often inefficient because it compiles code blocks that might only run once, leading to high memory usage with minimal performance gains.
2. Tracing JIT (Default)
Tracing JIT is much smarter and is the default setting in PHP 8. It looks for “hot paths” (frequently executed code segments, such as loops or repetitive method calls) during runtime.
- The engine starts by running code normally through the Zend VM.
- It monitors execution and identifies highly active code blocks (traces).
- Once a threshold is reached, the JIT compiler translates only that specific hot trace into machine code.
- Subsequent executions of that path run directly on the hardware.
Why JIT Doesn’t Make Every PHP App Faster
A common misconception is that PHP 8’s JIT compiler will instantly double the speed of all WordPress sites or web APIs. In reality, JIT has a very specific impact depending on the workload:
- I/O-Bound Applications (Most Web Apps): Standard web applications spend most of their execution time waiting for database queries, disk access, network requests, and external APIs. For these apps, the CPU is rarely the bottleneck, meaning the JIT compiler provides negligible or even slightly negative performance differences due to compilation overhead.
- CPU-Bound Applications: Applications that perform heavy mathematical calculations, data processing, 3D rendering, image manipulation, or machine learning spend almost all their time executing CPU instructions. In these scenarios, bypassing the Zend VM interpreter using JIT results in massive performance improvements, sometimes making the code run several times faster.