Skip to content

JIT 编译器

概述

JIT(Just-In-Time)编译器是 PHP 8.0 引入的革命性功能,集成在 OPcache 扩展中。JIT 编译器在运行时将 PHP 操作码(Opcode)进一步编译为本地机器码(Native Machine Code),直接由 CPU 执行,绕过 Zend 虚拟机的解释执行环节,从而显著提升 CPU 密集型任务的执行性能。

前置知识

阅读本节前,建议先了解:OPcache 配置详解OPcache 优化策略

基础概念

为什么需要 JIT

传统 PHP 执行流程:

PHP 源码 → Token → AST → Opcode → Zend VM 解释执行

JIT 编译后的执行流程:

PHP 源码 → Token → AST → Opcode → 机器码 → CPU 直接执行

JIT 编译的优势在于将频繁执行的"热路径"(Hot Path)代码编译为本地机器码,消除了 Zend VM 的指令分发开销和虚拟寄存器操作开销。

JIT vs OPcache

特性OPcacheJIT
工作阶段请求开始时运行时(动态)
输出操作码缓存本地机器码
执行方式Zend VM 解释执行CPU 直接执行
适用场景I/O 密集型CPU 密集型
性能提升3-10 倍(避免重复编译)额外 1.2-2 倍(CPU 密集任务)

关键区别

OPcache 解决的是"重复编译"的问题,JIT 解决的是"解释执行"的问题。两者互补,JIT 依赖 OPcache 提供的操作码。

JIT 的适用场景

JIT 在以下场景中表现优异:

  1. 数学计算:大量数值运算、统计计算
  2. 图像处理:像素级操作
  3. 加密/哈希:大量数据的加密解密
  4. 排序/搜索:大规模数据集的排序和搜索算法
  5. 编译器/解析器:语法分析、模板编译
  6. 游戏逻辑:游戏服务端的数值计算

JIT 在以下场景中提升不明显:

  1. I/O 密集型应用:等待数据库、HTTP 请求等
  2. 短生命周期脚本:执行时间太短,JIT 编译开销大于收益
  3. 框架引导开销:框架启动阶段的通用代码

详细说明

JIT 配置

opcache.jit

ini
; JIT 模式配置(PHP 8.0+)
opcache.jit = tracing

JIT 模式支持两种格式:

字符串格式(PHP 8.0+):

说明
off禁用 JIT(默认值)
on启用 JIT,使用默认配置
tracing使用 tracing 模式(推荐)
function使用 function 模式

数字格式(四位数字 CRTO):

opcache.jit = CRTO
名称说明
CCPU 优化0=无, 1=AVX
R寄存器分配0=无, 1=本地, 2=全局
T触发器0=关闭, 1=首次调用, 2=请求首次, 3=配置文件, 4=始终编译, 5=函数调用计数
O优化级别0=无, 1=最小, 2=优化块, 3=优化函数, 4=优化整个脚本, 5=基于 Tracing

常用 JIT 配置值

ini
; 禁用 JIT
opcache.jit = off
; 等同于 0
opcache.jit = 0

; 仅函数级 JIT(简单模式)
opcache.jit = function
; 等同于 1205
opcache.jit = 1205

; Tracing JIT(推荐配置)
opcache.jit = tracing
; 等同于 1254
opcache.jit = 1254

; 最大优化(适用于 CPU 密集型应用)
opcache.jit = 1254

; 调试用:最小优化 + 始终编译
opcache.jit = 0005

; 关闭优化但使用 tracing
opcache.jit = 0055

opcache.jit_buffer_size

ini
; JIT 代码缓冲区大小
opcache.jit_buffer_size = 256M

分配给 JIT 编译后机器码的共享内存大小。如果缓冲区满了,新的代码将无法被 JIT 编译。

场景建议值
Web 应用(I/O 密集)64M - 128M
混合型应用128M - 256M
CPU 密集型应用256M - 512M
禁用 JIT01M

缓冲区溢出

如果 JIT 缓冲区太小,JIT 编译器无法编译所有热路径代码。监控 opcache_get_status()['jit']['buffer_size']opcache_get_status()['jit']['buffer_used'] 来确定合适的大小。

Function 模式 vs Tracing 模式

Function 模式(function)

Function 模式以函数为单位进行 JIT 编译:

php
<?php
declare(strict_types=1);

/**
 * Function 模式编译整个函数
 * 无论函数内部哪些路径被执行,都会被编译
 */

function fibonacci(int $n): int
{
    if ($n <= 1) {
        return $n;
    }
    return fibonacci($n - 1) + fibonacci($n - 2);
}

// Function 模式会将整个 fibonacci 函数编译为机器码
// 包括可能从未执行的分支
echo fibonacci(30) . PHP_EOL;

Function 模式特点

  • 编译整个函数
  • 简单可靠
  • 不考虑运行时类型信息
  • 优化级别较低

Tracing 模式(tracing)

Tracing 模式追踪实际执行的热路径,仅编译频繁执行的代码路径:

php
<?php
declare(strict_types=1);

/**
 * Tracing 模式仅编译热路径
 */

function processItem(string|int $item): string
{
    // Tracing 模式会检测到 string 路径是热路径
    // 仅为 string 类型编译优化的机器码
    if (is_string($item)) {
        return strtoupper($item);
    }

    // 此路径如果很少执行,不会被 JIT 编译
    return (string) $item;
}

// 循环执行使得 string 路径成为热路径
for ($i = 0; $i < 100000; $i++) {
    processItem("item_{$i}");
}

Tracing 模式特点

  • 仅编译热路径(Hot Path)
  • 利用运行时类型信息
  • 优化级别更高
  • 自动去优化(Deoptimization)能力

推荐选择

对于大多数应用,推荐使用 Tracing 模式(opcache.jit = tracing),它在性能和稳定性之间取得了最佳平衡。

CRTO 位掩码详解

php
<?php
declare(strict_types=1);

/**
 * JIT 模式解析器
 */
class JitModeParser
{
    /**
     * 解析 JIT 模式字符串或数字
     */
    public static function parse(string|int $mode): array
    {
        if (is_string($mode)) {
            return match ($mode) {
                'off' => ['c' => 0, 'r' => 0, 't' => 0, 'o' => 0, 'desc' => '禁用 JIT'],
                'on' => self::parse(1254),
                'function' => self::parse(1205),
                'tracing' => self::parse(1254),
                default => throw new InvalidArgumentException("未知 JIT 模式: {$mode}"),
            };
        }

        $c = ($mode >> 12) & 0xF;
        $r = ($mode >> 8) & 0xF;
        $t = ($mode >> 4) & 0xF;
        $o = $mode & 0xF;

        return [
            'c' => $c,
            'r' => $r,
            't' => $t,
            'o' => $o,
            'desc' => sprintf('C=%d R=%d T=%d O=%d', $c, $r, $t, $o),
        ];
    }

    /**
     * 获取 CPU 优化说明
     */
    public static function cpuOptimization(int $c): string
    {
        return match ($c) {
            0 => '无 CPU 特定优化',
            1 => '启用 AVX 指令优化',
            default => "未知 ({$c})",
        };
    }

    /**
     * 获取寄存器分配说明
     */
    public static function registerAllocation(int $r): string
    {
        return match ($r) {
            0 => '无寄存器分配',
            1 => '本地寄存器分配',
            2 => '全局寄存器分配',
            default => "未知 ({$r})",
        };
    }

    /**
     * 获取触发器说明
     */
    public static function trigger(int $t): string
    {
        return match ($t) {
            0 => 'JIT 关闭',
            1 => '首次调用时编译',
            2 => '请求首次调用时编译',
            3 => '配置文件中标记的函数编译',
            4 => '始终编译所有函数',
            5 => '函数调用次数达到阈值时编译',
            default => "未知 ({$t})",
        };
    }

    /**
     * 获取优化级别说明
     */
    public static function optimizationLevel(int $o): string
    {
        return match ($o) {
            0 => '无优化(仅转译)',
            1 => '最小优化',
            2 => '优化基本块',
            3 => '优化函数',
            4 => '优化整个脚本',
            5 => '基于 Tracing 的优化',
            default => "未知 ({$o})",
        };
    }
}

// 解析当前 JIT 配置
$parsed = JitModeParser::parse((string) ini_get('opcache.jit'));
echo "当前 JIT 配置: {$parsed['desc']}" . PHP_EOL;
echo "CPU 优化: " . JitModeParser::cpuOptimization($parsed['c']) . PHP_EOL;
echo "寄存器分配: " . JitModeParser::registerAllocation($parsed['r']) . PHP_EOL;
echo "触发器: " . JitModeParser::trigger($parsed['t']) . PHP_EOL;
echo "优化级别: " . JitModeParser::optimizationLevel($parsed['o']) . PHP_EOL;

实战示例

性能对比测试

数学运算性能

php
<?php
declare(strict_types=1);

/**
 * JIT 性能对比 - 数学运算
 */
class JitBenchmark
{
    private const ITERATIONS = 1_000_000;

    /**
     * 斐波那契数列计算
     */
    public static function fibonacci(int $n): int
    {
        if ($n <= 1) {
            return $n;
        }
        $a = 0;
        $b = 1;
        for ($i = 2; $i <= $n; $i++) {
            $temp = $a + $b;
            $a = $b;
            $b = $temp;
        }
        return $b;
    }

    /**
     * 素数筛法
     */
    public static function sieveOfEratosthenes(int $limit): int
    {
        $sieve = array_fill(0, $limit + 1, true);
        $sieve[0] = false;
        $sieve[1] = false;

        for ($i = 2; $i * $i <= $limit; $i++) {
            if ($sieve[$i]) {
                for ($j = $i * $i; $j <= $limit; $j += $i) {
                    $sieve[$j] = false;
                }
            }
        }

        return array_count_values($sieve)[true] ?? 0;
    }

    /**
     * 矩阵乘法
     */
    public static function matrixMultiply(int $size): float
    {
        $a = [];
        $b = [];
        for ($i = 0; $i < $size; $i++) {
            for ($j = 0; $j < $size; $j++) {
                $a[$i][$j] = mt_rand(0, 100) / 100.0;
                $b[$i][$j] = mt_rand(0, 100) / 100.0;
            }
        }

        $start = hrtime(true);
        $result = [];
        for ($i = 0; $i < $size; $i++) {
            for ($j = 0; $j < $size; $j++) {
                $sum = 0.0;
                for ($k = 0; $k < $size; $k++) {
                    $sum += $a[$i][$k] * $b[$k][$j];
                }
                $result[$i][$j] = $sum;
            }
        }
        $end = hrtime(true);

        return ($end - $start) / 1_000_000_000;
    }

    /**
     * 运行所有基准测试
     */
    public static function runAll(): array
    {
        $results = [];

        // 斐波那契
        $start = hrtime(true);
        for ($i = 0; $i < 100; $i++) {
            self::fibonacci(50);
        }
        $results['fibonacci'] = (hrtime(true) - $start) / 1_000_000 . 'ms';

        // 素数筛
        $start = hrtime(true);
        self::sieveOfEratosthenes(1_000_000);
        $results['sieve'] = (hrtime(true) - $start) / 1_000_000 . 'ms';

        // 矩阵乘法
        $results['matrix'] = self::matrixMultiply(200) * 1000 . 'ms';

        // JIT 状态
        $status = opcache_get_status(false);
        $results['jit_enabled'] = ini_get('opcache.jit');
        $results['jit_buffer'] = ini_get('opcache.jit_buffer_size');
        $results['php_version'] = PHP_VERSION;

        return $results;
    }
}

if (PHP_SAPI === 'cli') {
    // 先预热(让 JIT 有机会编译热路径)
    for ($i = 0; $i < 10; $i++) {
        JitBenchmark::fibonacci(50);
    }

    // 运行基准测试
    $results = JitBenchmark::runAll();
    echo "=== JIT 性能基准测试 ===" . PHP_EOL;
    echo "PHP 版本: {$results['php_version']}" . PHP_EOL;
    echo "JIT 模式: {$results['jit_enabled']}" . PHP_EOL;
    echo "JIT 缓冲区: {$results['jit_buffer']}" . PHP_EOL;
    echo PHP_EOL;
    echo "斐波那契(50) x100: {$results['fibonacci']}" . PHP_EOL;
    echo "素数筛(100万): {$results['sieve']}" . PHP_EOL;
    echo "200x200 矩阵乘法: {$results['matrix']}" . PHP_EOL;
}

字符串处理性能

php
<?php
declare(strict_types=1);

/**
 * JIT 性能对比 - 字符串处理
 */
class StringJitBenchmark
{
    /**
     * 字符串搜索与替换
     */
    public static function stringReplace(): float
    {
        $text = str_repeat('The quick brown fox jumps over the lazy dog. ', 1000);
        $iterations = 10_000;

        $start = hrtime(true);
        for ($i = 0; $i < $iterations; $i++) {
            str_replace('fox', 'cat', $text);
            str_replace('lazy', 'energetic', $text);
            str_replace('dog', 'wolf', $text);
        }

        return (hrtime(true) - $start) / 1_000_000;
    }

    /**
     * 正则表达式匹配
     */
    public static function regexMatch(): float
    {
        $text = str_repeat('Hello World 123 foo bar 456 baz 789 qux ', 100);
        $iterations = 5_000;

        $start = hrtime(true);
        for ($i = 0; $i < $iterations; $i++) {
            preg_match_all('/\d+/', $text, $matches);
            preg_replace('/\b\w{3}\b/', 'XXX', $text);
        }

        return (hrtime(true) - $start) / 1_000_000;
    }

    /**
     * JSON 编码/解码
     */
    public static function jsonBench(): float
    {
        $data = [];
        for ($i = 0; $i < 1000; $i++) {
            $data[] = [
                'id' => $i,
                'name' => "Item {$i}",
                'price' => mt_rand(100, 10000) / 100,
                'tags' => ['tag_a', 'tag_b', 'tag_c'],
                'active' => (bool) ($i % 2),
            ];
        }
        $iterations = 5_000;

        $start = hrtime(true);
        for ($i = 0; $i < $iterations; $i++) {
            $json = json_encode($data);
            json_decode($json, true);
        }

        return (hrtime(true) - $start) / 1_000_000;
    }
}

// 预热
for ($i = 0; $i < 5; $i++) {
    StringJitBenchmark::stringReplace();
    StringJitBenchmark::regexMatch();
    StringJitBenchmark::jsonBench();
}

// 运行测试
echo "字符串替换 x10000: " . StringJitBenchmark::stringReplace() . "ms" . PHP_EOL;
echo "正则匹配 x5000: " . StringJitBenchmark::regexMatch() . "ms" . PHP_EOL;
echo "JSON 编解码 x5000: " . StringJitBenchmark::jsonBench() . "ms" . PHP_EOL;

Mandelbrot 分形计算

php
<?php
declare(strict_types=1);

/**
 * Mandelbrot 分形计算 - JIT 性能典型场景
 */
class MandelbrotRenderer
{
    /**
     * 计算 Mandelbrot 集合
     */
    public function render(int $width, int $height, int $maxIter): array
    {
        $result = [];
        $minX = -2.5;
        $maxX = 1.0;
        $minY = -1.25;
        $maxY = 1.25;

        for ($y = 0; $y < $height; $y++) {
            for ($x = 0; $x < $width; $x++) {
                $cx = $minX + ($maxX - $minX) * $x / $width;
                $cy = $minY + ($maxY - $minY) * $y / $height;

                $zx = 0.0;
                $zy = 0.0;
                $iter = 0;

                while ($zx * $zx + $zy * $zy <= 4.0 && $iter < $maxIter) {
                    $tmp = $zx * $zx - $zy * $zy + $cx;
                    $zy = 2.0 * $zx * $zy + $cy;
                    $zx = $tmp;
                    $iter++;
                }

                $result[$y][$x] = $iter;
            }
        }

        return $result;
    }
}

// 基准测试
$renderer = new MandelbrotRenderer();

// 预热
$renderer->render(100, 100, 50);

// 正式测试
$start = hrtime(true);
$renderer->render(1000, 1000, 256);
$time = (hrtime(true) - $start) / 1_000_000;

echo "Mandelbrot 1000x1000 maxIter=256: {$time}ms" . PHP_EOL;
echo "JIT 模式: " . ini_get('opcache.jit') . PHP_EOL;

JIT 监控

php
<?php
declare(strict_types=1);

/**
 * JIT 运行时状态监控
 */
class JitMonitor
{
    public static function getStatus(): ?array
    {
        $status = opcache_get_status(false);
        return $status['jit'] ?? null;
    }

    public static function report(): void
    {
        $jit = self::getStatus();

        if ($jit === null) {
            echo "JIT 未启用" . PHP_EOL;
            return;
        }

        echo "=== JIT 状态报告 ===" . PHP_EOL;
        echo PHP_EOL;

        echo "缓冲区使用:" . PHP_EOL;
        $bufferUsed = ($jit['buffer_used'] ?? 0) / 1024 / 1024;
        $bufferSize = ($jit['buffer_size'] ?? 0) / 1024 / 1024;
        $bufferFree = ($jit['buffer_free'] ?? 0) / 1024 / 1024;
        echo "  总大小: " . round($bufferSize, 2) . "MB" . PHP_EOL;
        echo "  已使用: " . round($bufferUsed, 2) . "MB" . PHP_EOL;
        echo "  空闲: " . round($bufferFree, 2) . "MB" . PHP_EOL;

        if ($bufferSize > 0) {
            $usage = ($bufferUsed / $bufferSize) * 100;
            echo "  使用率: " . round($usage, 2) . "%" . PHP_EOL;
        }

        echo PHP_EOL;

        if (isset($jit['threads'])) {
            echo "线程数: {$jit['threads']}" . PHP_EOL;
        }

        echo PHP_EOL;
        echo "JIT 配置: " . ini_get('opcache.jit') . PHP_EOL;
        echo "JIT 缓冲区大小: " . ini_get('opcache.jit_buffer_size') . PHP_EOL;
    }
}

if (PHP_SAPI === 'cli') {
    JitMonitor::report();
}

注意事项

JIT 的局限性

  1. I/O 等待:JIT 不能加速 I/O 操作(数据库查询、HTTP 请求等)
  2. 启动开销:JIT 编译本身需要时间和内存
  3. 内存占用:JIT 缓冲区需要额外的共享内存
  4. 调试困难:JIT 编译后调试信息可能不准确
  5. 不适用所有代码:执行次数少的代码不会被 JIT 编译

开发与调试

ini
; 开发环境 - 禁用 JIT 避免干扰调试和 Xdebug
opcache.jit = off
opcache.jit_buffer_size = 0

; 性能测试环境 - 启用 JIT
opcache.jit = tracing
opcache.jit_buffer_size = 256M

JIT 与 Xdebug 冲突

使用 Xdebug 调试时应关闭 JIT。Xdebug 会干扰 JIT 的正常工作,可能导致异常行为或崩溃。建议在开发环境禁用 JIT,在性能测试环境禁用 Xdebug。

JIT 不适用的场景

php
<?php
declare(strict_types=1);

/**
 * 以下代码模式 JIT 优化效果有限
 */

// 1. I/O 密集型操作
function fetchUserData(PDO $pdo, int $userId): array
{
    $stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
    $stmt->execute([$userId]);
    return $stmt->fetch(PDO::FETCH_ASSOC);
    // JIT 无法加速数据库查询
}

// 2. 短生命周期脚本
function quickTask(): void
{
    echo "Hello World";
    // JIT 编译开销可能超过函数本身执行时间
}

// 3. 动态代码(eval)
function dynamicCode(string $code): mixed
{
    return eval($code);
    // eval 创建的代码不会被 JIT 编译
}

// 4. 频繁调用但包含大量 I/O
function processExternalApi(): void
{
    $response = file_get_contents('https://api.example.com/data');
    // JIT 无法加速网络等待时间
}

最佳实践

1. 按场景选择 JIT 模式

ini
; Web 应用(Laravel/Symfony 等)- I/O 密集
opcache.jit = tracing
opcache.jit_buffer_size = 64M

; CLI 守护进程(队列 worker)- 混合型
opcache.jit = tracing
opcache.jit_buffer_size = 128M

; CPU 密集型任务(数据处理、图像处理)
opcache.jit = 1254
opcache.jit_buffer_size = 256M

; 开发环境
opcache.jit = off
opcache.jit_buffer_size = 0

2. 监控 JIT 缓冲区使用

php
<?php
declare(strict_types=1);

// 定期检查 JIT 缓冲区使用率
$jit = opcache_get_status(false)['jit'] ?? null;
if ($jit !== null) {
    $usage = ($jit['buffer_used'] ?? 0) / ($jit['buffer_size'] ?? 1) * 100;
    if ($usage > 90) {
        // 告警:JIT 缓冲区即将耗尽
        error_log("JIT buffer usage: {$usage}%, consider increasing jit_buffer_size");
    }
}

3. 预热关键函数

php
<?php
declare(strict_types=1);

/**
 * JIT 预热 - 确保关键函数被 JIT 编译
 */
function jitWarmup(): void
{
    $iterations = 50; // 足够让 JIT 识别热路径

    // 预热计算密集型函数
    for ($i = 0; $i < $iterations; $i++) {
        // 在此调用需要 JIT 编译的关键函数
    }
}

4. 性能测试方法论

1. 确保测试环境一致(PHP 版本、配置、硬件)
2. 先运行预热迭代
3. 多次运行取中位数
4. 分别测试 JIT on/off 的结果
5. 关注实际业务场景的代码模式

下一节

继续学习:XHProf 性能分析

参考链接