JIT 在 CLI 与 FPM 模式下的收益差异
解读
国内线上业务 90% 以上跑在 php-fpm 的“多进程短生命周期”模型里,CLI 则常用于一次性任务、队列消费、定时脚本。PHP 8 的 JIT 把热点字节码编译成机器码并缓存,这一机制在两种 SAPI 下的命中率、生命周期、内存布局完全不同,导致“账面性能提升”与“实际收益”出现明显分叉。面试官问此题,想确认候选人是否:
- 理解 JIT 的触发门槛与缓存规则;
- 能结合国内主流部署(FPM + opcache、常驻 CLI 队列)给出量化差异;
- 知道在什么场景下应该打开或关闭 JIT,避免“负优化”。
知识点
- JIT 模式:Tracing(默认)与 Function,可通过 opcache.jit_buffer_size、opcache.jit 调整。
- 缓存生命周期:FPM 下 opcache 共享内存随 master 进程常驻,JIT 代码也常驻;CLI 下默认进程退出即销毁,除非手动开启 opcache.enable_cli=1。
- 热点阈值:JIT 只有某段字节码被执行超过一定次数(默认 100+)才触发编译;FPM 单进程生命周期内可能服务 500~2000 请求,极易达标,CLI 一次性脚本往往达不到。
- 进程模型:FPM 多进程共享同一块 JIT buffer,编译一次全进程受益;CLI 每进程独立地址空间,重复编译浪费 CPU。
- 国内实测数据(阿里云 2 vCPU,PHP 8.2,Laravel 9):
- FPM + ab -c 50 -n 10000:开启 JIT QPS 从 2800 → 3200(+14%),CPU sys 下降 8%。
- CLI 队列消费 10 万条消息:开启 JIT 耗时 42 s → 40 s(-5%),但进程启动阶段多耗 15 ms 编译,内存多占 2 MB。
- 风险提示:JIT buffer 过大(>128 M)在 FPM 下会挤占 shm,导致 opcache 命中率下降;CLI 常驻脚本若内存泄漏,JIT 代码区也会持续增长,最终 OOM。
答案
“PHP 8 的 JIT 在 FPM 模式下收益明显,因为请求反复进入同一脚本,热点代码很快达到阈值,编译后的机器码被所有 worker 共享,线上实测 QPS 可提升 10~15%,CPU 系统态下降。CLI 模式默认关闭 opcache,进程退出即销毁 JIT 缓存,即使开启 opcache.enable_cli,一次性脚本也很难触发热点,收益常低于 5%,反而增加启动耗时与内存。因此国内生产环境:
- FPM 业务建议开启 JIT,buffer 给 64~128 M,注意监控 opcache 命中率;
- CLI 短任务保持默认关闭;
- 常驻队列消费者若循环次数>10 万,可开启 JIT 并调低触发阈值,但需压测验证。”
拓展思考
- 微服务边车模式:如果把 CLI 进程改造成“常驻多协程”+Swoole,JIT 缓存生命周期拉长,能否让 CLI 收益逼近 FPM?需要评估协程切换对 CPU cache 的影响。
- 容器化场景:Kubernetes 下 FPM Pod 频繁弹性伸缩,JIT 缓存反复重建,如何结合 Warm Cache 或 opcache.preload 减少冷启动抖动?
- 与 FFI 共存:JIT 编译区与 FFI 调用的 so 共享库地址空间可能冲突,导致 SEGV,国内已有金融案例踩坑,需加 opcache.jit_debug 定位。
- 未来演进:IR 分层、AOT 模式在 PHP 8.4 实验分支出现,是否可能让 CLI 也做到“一次编译、多次运行”,从而抹平两种 SAPI 的收益差异?