0Pricing
PHP Academy · 课时

OPcache 与 JIT 编译

使用字节码缓存和 JIT 加速 PHP

OPcache 与 JIT 编译 是 CoddyKit 上的免费 PHP Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 PHP Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 PHP Academy 课程共包含 4 节课。

缓存编译结果

OPcache 会将已编译的操作码存储在共享内存中,使 PHP 无需在每次请求中重复进行词法分析、语法分析和编译。在此基础上,即时编译(自 PHP 8.0 起)会将热点操作码编译为本机机器码。二者共同构成 PHP 性能的基础。

本课将介绍 OPcache 配置、预加载和即时编译调优。

OPcache 的作用

每个文件的已编译 op_array 都会缓存到一个由所有 FPM 工作进程共享的内存段中。命中缓存的请求可以直接复用操作码。OPcache 还会在首次编译文件时运行优化器(常量折叠、死代码消除和操作码融合)。

; Core OPcache config (php.ini)
opcache.enable = 1
opcache.memory_consumption = 256      ; MB of shared memory
opcache.interned_strings_buffer = 16  ; MB for interned strings
opcache.max_accelerated_files = 20000 ; cap on cached scripts

时间戳验证

validate_timestamps 决定 OPcache 是否重新检查文件的修改时间。在开发环境中,请保持该选项开启,并将 revalidate_freq 设置为较短的间隔;在生产环境中,将其设置为0,这样 PHP 就不会检查文件状态,部署时通过清除缓存(或重新构建镜像)完成更新。

; Production: never re-stat files
opcache.validate_timestamps = 0

; Development: check every 2 seconds
; opcache.validate_timestamps = 1
; opcache.revalidate_freq = 2

检查缓存

opcache_get_status() 会报告命中率、内存使用量和缓存脚本;opcache_get_configuration() 会显示配置。命中率低或频繁重启通常意味着内存太少或文件过多。

<?php
if (function_exists('opcache_get_status')) {
    $s = opcache_get_status(false);
    if ($s) {
        printf("hits:   %d\n", $s['opcache_statistics']['hits']);
        printf("misses: %d\n", $s['opcache_statistics']['misses']);
        printf("hit %%: %.2f\n", $s['opcache_statistics']['opcache_hit_rate']);
        printf("used:   %.1f MB\n", $s['memory_usage']['used_memory'] / 1048576);
    } else {
        echo "OPcache enabled but no status (likely CLI)\n";
    }
} else {
    echo "OPcache not available\n";
}
?>

预加载

预加载(PHP 7.4+)会在服务器启动时编译一组类和函数,并将它们永久保留在内存中,使每个请求都能使用它们,而无需自动加载或在每次请求中重新链接。请将 opcache.preload 指向一个使用 require 引入要预热文件的脚本。

; php.ini
opcache.preload = /app/preload.php
opcache.preload_user = www-data

编写预加载脚本

预加载脚本会在主进程中运行。您可以使用 opcache_compile_file() 加载类而不执行它们,也可以直接使用 require 引入它们。框架会生成预加载列表;下面是其中一种结构。

<?php
// preload.php — runs once at startup
$dir = '/app/src';
$rii = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator($dir)
);

foreach ($rii as $file) {
    if ($file->getExtension() === 'php') {
        // Compiles + links the class into shared memory
        opcache_compile_file($file->getPathname());
    }
}
?>

进入即时编译

即使有缓存的操作码,VM 仍会解释执行它们。即时编译(JIT)会将热点操作码序列编译为本机 CPU 指令,从而消除 VM 分派开销。对于受 CPU 限制的数值代码,收益很大;对于典型的 I/O 密集型 Web 应用,收益较小(这类应用的耗时主要在数据库和网络,而不是 CPU)。

配置即时编译

即时编译位于 OPcache 内部,因此必须启用 OPcache。opcache.jit_buffer_size 会为生成的机器码分配空间(设置为 0 会禁用即时编译)。opcache.jit 是一个控制触发条件和优化策略的四位模式。

; Enable JIT (requires opcache.enable=1)
opcache.jit_buffer_size = 128M
opcache.jit = tracing        ; alias for 1254, the common default

; The 4 digits CRTO mean:
;   C - CPU optimization flags
;   R - register allocation
;   T - trigger (when to JIT)
;   O - optimization level

跟踪式即时编译与函数式即时编译

有两种策略:函数式即时编译(模式 1205)会在函数首次调用时编译整个函数;跟踪式即时编译(1254,默认的 tracing 别名)会分析执行情况并编译热点循环和路径。由于它会根据观测到的类型进行特化,通常能取得更好的效果。跟踪式即时编译是推荐的默认设置。

; Tracing JIT (recommended) — compiles hot traces
opcache.jit = tracing

; Function JIT — compiles entire functions when first called
; opcache.jit = function

; Disable JIT but keep opcode cache
; opcache.jit_buffer_size = 0

衡量即时编译的影响

即时编译在密集的数值循环中最能发挥作用。这个自包含的基准测试(包含一个类似 Mandelbrot 的内层循环)代表了即时编译可以大幅缩短运行时间的 CPU 密集型工作负载;请分别使用和不使用 jit_buffer_size 运行它进行比较。

<?php
function mandelEscape(float $cr, float $ci, int $max): int {
    $zr = 0.0; $zi = 0.0; $n = 0;
    while ($n < $max && ($zr * $zr + $zi * $zi) <= 4.0) {
        $t  = $zr * $zr - $zi * $zi + $cr;
        $zi = 2.0 * $zr * $zi + $ci;
        $zr = $t;
        $n++;
    }
    return $n;
}

$start = hrtime(true);
$sum = 0;
for ($y = 0; $y < 200; $y++) {
    for ($x = 0; $x < 200; $x++) {
        $sum += mandelEscape($x / 100 - 2, $y / 100 - 1, 256);
    }
}
printf("sum=%d  time=%.2f ms\n", $sum, (hrtime(true) - $start) / 1e6);
?>

何时不应期待收益(NOT)

对于查询数据库、渲染模板并返回 JSON 的典型请求,CPU 几乎不进行原始算术运算,即时编译可能只能带来个位数百分比的提升,甚至没有提升。请先优先做好 OPcache、预加载以及数据库和缓存的使用。对于图像处理、数学运算、解析器和其他计算密集型代码,再进行即时编译调优。

快速检查

相比普通的 OPcache 缓存,预加载能为您提供什么额外优势?

回顾

OPcache 会在共享内存中缓存已编译的 op_arrays 并运行优化器;请在生产环境中将 validate_timestamps=0,并合理设置 memory_consumption/max_accelerated_files。预加载会在启动时让选定的类常驻内存。即时编译(位于 OPcache 内部,需要 jit_buffer_size)会将热点代码编译为本机指令——对 CPU 密集型数学运算提升显著,对 I/O 密集型 Web 应用提升很小。请使用 opcache_get_status() 进行验证。

常见问题解答

「OPcache 与 JIT 编译」课时是免费的吗?

是的 — 「OPcache 与 JIT 编译」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 PHP Academy 课程的其余内容,请升级到 CoddyKit PRO。 PHP Academy 课程共包含 4 节课。

「OPcache 与 JIT 编译」这节课中我会学到什么?

使用字节码缓存和 JIT 加速 PHP 你通过在浏览器中直接运行的动手代码来练习 PHP Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 PHP Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 PHP Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「OPcache 与 JIT 编译」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 PHP Academy 课中编写并运行代码吗?

能。每节 PHP Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. Zend 引擎的工作原理
  2. 内存管理与垃圾回收
  3. OPcache 与 JIT 编译
  4. 使用 C 编写基础 PHP 扩展
← 返回 PHP Academy