去年双11大促,我们团队负责的优惠券计算服务突然扛不住了。服务器CPU冲到95%,响应时间从50ms飙到800ms。代码是纯PHP写的,没有数据库查询,没有外部API调用,就是一堆数学运算和条件判断。当时第一反应是加机器,但成本太高。
后来同事提到PHP8的JIT,我半信半疑。毕竟网上都说JIT对Web应用提升不大。但死马当活马医,我花了三天配置、压测、调优。结果让我震惊:CPU密集型场景下,JIT让吞吐量提升了40%。
这篇文章就是那次实战的完整记录。我会告诉你JIT怎么配、配完效果如何、以及我踩过的三个坑。
PHP传统执行流程:源码 → 词法分析 → AST → Opcode → 执行。每次请求都要重复这个流程,即使OpCache缓存了Opcode,但Opcode还是被解释执行,没有编译成机器码。
JIT(Just-In-Time)编译器在Opcode基础上,识别出“热点代码”(频繁执行的代码段),将其编译成机器码直接执行。跳过解释器,CPU直接跑机器码,性能自然提升。
但JIT不是银弹。它主要优化CPU密集型任务(数学运算、循环、算法),对I/O密集型(数据库查询、文件读写、网络请求)几乎没效果。因为I/O等待时间远大于CPU执行时间,JIT省下的那点CPU时间被I/O吞没了。
我测试了三种场景:
测试环境:
<?php
// cpu_intensive.php
// 计算第40个斐波那契数,递归实现,纯CPU计算
function fibonacci($n) {
if ($n <= 1) {
return $n;
}
return fibonacci($n - 1) + fibonacci($n - 2);
}
$start = microtime(true);
$result = fibonacci(40);
$end = microtime(true);
echo "Result: $result\n";
echo "Time: " . round(($end - $start) * 1000, 2) . " ms\n";
<?php
// io_intensive.php
// 读取1000次同一个文件,模拟I/O密集型
$filename = '/tmp/testfile.txt';
// 生成测试文件
file_put_contents($filename, str_repeat('A', 1024 * 1024)); // 1MB文件
$start = microtime(true);
for ($i = 0; $i < 1000; $i++) {
$content = file_get_contents($filename);
// 模拟处理
$len = strlen($content);
}
$end = microtime(true);
echo "Time: " . round(($end - $start) * 1000, 2) . " ms\n";
#!/bin/bash
# benchmark.sh
# 并发100,请求1000次
echo "=== CPU密集型测试 ==="
ab -n 1000 -c 100 http://localhost/cpu_intensive.php
echo "=== I/O密集型测试 ==="
ab -n 1000 -c 100 http://localhost/io_intensive.php
; php.ini JIT配置
opcache.enable=1
opcache.jit=tracing
opcache.jit_buffer_size=100M
; 其他推荐配置
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.revalidate_freq=2
<?php
// check_jit.php
phpinfo();
// 查看JIT部分:JIT enabled, JIT buffer size, JIT type
或者命令行:
php -i | grep -i jit
# 输出示例:
# JIT => On
# JIT buffer size => 100M
# JIT type => Tracing
| 方案 | 单次执行时间 | QPS(每秒请求数) | 平均响应时间 | CPU使用率 |
|---|---|---|---|---|
| JIT关闭 | 820ms | 1.2 | 833ms | 95% |
| JIT tracing | 490ms | 2.0 | 500ms | 92% |
| JIT function | 510ms | 1.9 | 526ms | 93% |
结论:JIT tracing模式比关闭快40%,function模式快38%。tracing略优。
| 方案 | 单次执行时间 | QPS | 平均响应时间 | CPU使用率 |
|---|---|---|---|---|
| JIT关闭 | 320ms | 3.1 | 322ms | 30% |
| JIT tracing | 305ms | 3.2 | 312ms | 28% |
| JIT function | 310ms | 3.2 | 315ms | 29% |
结论:I/O密集型场景,JIT提升约5%,几乎可以忽略。
| 方案 | 进程内存(MB) | JIT缓冲区(MB) |
|---|---|---|
| JIT关闭 | 12.3 | 0 |
| JIT tracing | 18.7 | 100(配置) |
| JIT function | 17.2 | 100(配置) |
JIT开启后,每个PHP进程多占用约5-6MB内存,加上JIT缓冲区100MB(共享内存),整体内存开销增加。对于内存敏感的服务器,需要权衡。
第一次配置完php.ini,重启php-fpm,用phpinfo()查看,JIT显示为“Off”。查了半天,发现是OpCache没开启。JIT依赖OpCache,必须先开启OpCache。正确顺序:
# 检查OpCache是否加载
php -m | grep opcache
# 如果没有,编译PHP时加上 --enable-opcache
# 确保php.ini中有:
; opcache.enable=1
默认JIT buffer_size是0,意味着JIT不生效。我一开始设了32M,压测时发现QPS提升只有10%。后来用phpinfo()查看JIT统计,发现“JIT buffer free”经常为0,说明缓冲区满了,热点代码被丢弃。改成100M后,提升到了40%。
建议:根据你的代码复杂度设置,一般64M-128M。可以用以下脚本监控:
<?php
// monitor_jit.php
$jit = opcache_get_status()['jit'];
echo "Buffer size: " . $jit['buffer_size'] . "\n";
echo "Buffer free: " . $jit['buffer_free'] . "\n";
echo "Compiled scripts: " . $jit['compiled_scripts'] . "\n";
echo "Compiled functions: " . $jit['compiled_functions'] . "\n";
开发环境我习惯开Xdebug调试,结果JIT死活不生效。查文档发现,Xdebug会禁用JIT。解决方案:生产环境关闭Xdebug,开发环境如果非要调试,可以临时关闭JIT。
# 生产环境php.ini
; 注释掉Xdebug
; zend_extension=xdebug.so
; 开启JIT
opcache.jit=tracing
opcache.jit_buffer_size=100M
# 开发环境php.ini
; 开启Xdebug
zend_extension=xdebug.so
; 关闭JIT
opcache.jit=disable
最后,JIT不是魔法。它优化的是PHP执行本身,而不是你的业务逻辑。如果你的代码本身有N+1查询、死循环、内存泄漏,先修这些,再考虑JIT。
专注技术分享与实战