学完这篇,你能在一台运行 PHP 的机器上把请求拆开看清时间花在哪:用 Xdebug 做函数级剖析、用 Tideways 做低开销长期采样,并准确判断一个慢函数是否真的需要优化。
第一步:先分清 Xdebug 和 Tideways 的定位
两者都能输出「函数 → 耗时 / 调用次数」的剖析数据,差别在开销和用法:
- Xdebug:功能全(断点、单步、覆盖率、profile),但开销大,profile 模式下单个请求慢 3-10 倍很常见。适合本机、单请求、复现问题。
- Tideways XHProf:就是老的 XHProf 换了个维护方,开销低得多(约 10%-50%,看采样标志),适合测试环境长期运行、随机采样线上问题。这里的 Tideways 指开源扩展
tideways_xhprof;它家还有一套商业 APM(tideways-php + tideways-daemon),自动采样并提供面板,原理一致,本文只讲可自建的部分。
原则:先用低成本手段定位到「哪个环节慢」,再用 Xdebug 深挖那一段。反过来做,你会在几百兆的 cachegrind 文件里迷失。
第二步:装 Xdebug 并只在你需要时开启
pecl install xdebug
php -m | grep xdebug # 确认加载成功
php.ini 里加(Xdebug 3 的写法,2.x 的 xdebug.profiler_enable 已废弃):
zend_extension=xdebug.so
xdebug.mode=profile
xdebug.output_dir=/tmp/xdebug
xdebug.profiler_output_name=cachegrind.out.%p
xdebug.start_with_request=trigger
start_with_request=trigger 是关键:默认不剖析,只有请求带上触发参数(默认 XDEBUG_PROFILE)才记录。
https://你的站/index.php?XDEBUG_PROFILE=1
# CLI
XDEBUG_PROFILE=1 php /path/to/script.php
会在 /tmp/xdebug 生成 cachegrind.out.12345。用 QCachegrind / KCachegrind 打开,或丢给 Webgrind 看网页版。
注意:先 mkdir /tmp/xdebug && chmod 777 /tmp/xdebug,否则 PHP-FPM 用户写不进去,你会以为「没生成数据」,其实日志里是权限错误。
注意:剖析期间保证同一个 URL、同一份数据、单用户访问。你一边刷新列表页一边跑剖析,两次结果能差一倍,这不是代码问题。
第三步:用 tideways_xhprof 做可长期开启的采样
pecl install tideways_xhprof
php.ini 加 extension=tideways_xhprof.so。它不会自动采集,需要你在代码里手动埋点。对无框架、单入口的 PHP 系统(比如 Clara BBS 这类纯 PHP 项目),最省事的做法是在入口文件最前面和最后面各加几行:
// 入口文件开头
if (getenv('PROF_ON')) {
tideways_xhprof_enable(TIDEWAYS_XHPROF_FLAGS_CPU | TIDEWAYS_XHPROF_FLAGS_MEMORY);
}
// 结尾(或 register_shutdown_function 里)
if (getenv('PROF_ON')) {
$data = tideways_xhprof_disable();
file_put_contents('/tmp/xhprof/' . uniqid('p', true) . '.json', json_encode($data));
}
分析端推荐 XHGui:它读这些 JSON、存进 MongoDB,提供按 URL 聚合的调用树和时间轴,能看到「哪个页面的哪个函数最近变慢了」,这是 Xdebug 单次剖析给不了的。
注意:Xdebug 和 tideways_xhprof 别同时开。两个扩展叠加会让数据既慢又不准,排查时先注释掉一个。
注意:TIDEWAYS_XHPROF_FLAGS_CPU 记录 CPU 时间,_MEMORY 记录内存峰值变化。只想看耗时就用 FLAGS_CPU,数据量小一半。
第四步:怎么看数据(这步比装扩展重要)
打开剖析结果,按 Self(自身耗时) 排序,而不是 Inclusive(含子调用)。判断顺序:
- Self 高 + 调用次数巨大的函数 = 真热点。比如
mysqli_query Self 占总时间 60%,说明时间在等数据库,不关 PHP 事。
- Self 低但 Inclusive 高 = 只是调用链的壳子,往下钻。
- 调用次数异常:一个页面里
PDO::prepare 被调 300 次,那不是它慢,是你有 N+1 查询。
对应到这类无框架系统上,几个高频落点:
- 列表页:版块/用户/附件信息在循环里逐条查库 → 抓调用次数暴增的 SQL 函数
- 帖子页:
file_get_contents / curl_exec 出现在渲染路径上(外部接口、网盘信息同步)
- 后台:
json_encode / serialize 处理大数组,Self 占比可能超过 20%
- 插件钩子多的页面:可以在钩子执行前后手动分段计时(
microtime(true) 差值写日志),快速确认是「主流程慢」还是「某个插件慢」
第五步:拿着证据改,再复测
典型修复手段:把循环内查询改成一次 IN 批量查;外部请求改异步或加缓存;大数组不用 json_encode 反复序列化同一份数据;给慢 SQL 补索引。改完用同样的触发方式和数据再跑一次剖析,对比 Self 排行,别凭感觉说「快了」。
注意:线上环境永远不要开 xdebug.mode=profile。要线上抓问题,用 tideways_xhprof + 低频率采样(比如每 100 个请求采 1 个)。
小结
- Xdebug 用于本机深挖单次请求,用
start_with_request=trigger 按需触发,别常开。
- Tideways XHProf 用于测试环境长期采样,代码里 enable/disable 埋点,配 XHGui 看趋势。
- 两者不要同时启用;剖析时固定 URL、固定数据、单人访问。
- 读数据看 Self 耗时 + 调用次数:Self 高是热点,次数多是有 N+1 或重复计算。
- 每次优化前后用同样的方式复测,用数据收尾而不是用感觉收尾。