很多 PHP 项目并不是一开始就要拼极致性能,但当页面已经出现明显变慢、请求发卡、接口响应不稳定时,只靠“加机器、扩内存”通常只能暂时缓解。要判断问题到底出在数据库、函数调用链,还是某段业务代码本身,更直接的办法是先做一次性能分析,再决定优化方向。
XHProf 是 Facebook 推出的一个被动分析器,适合用来追踪 PHP 程序的执行耗时、CPU 消耗和内存使用情况,也能在生产环境使用。下面按“什么时候该分析、怎么安装、如何接入项目、结果怎么看”这条线梳理,方便你快速上手并定位代码瓶颈。
什么时候该做性能分析
开发中经常会遇到一种情况:CPU 正常、内存正常、数据库看上去也没异常,但网站打开还是慢,接口调用还是卡。这类问题如果只靠经验排查,往往要花很多时间,而且不一定能抓到真正的热点。
在硬件成本越来越低的情况下,很多团队会优先选择“空间换时间”,比如 8G 不够就换 16G,一台机器扛不住就再加两台。这种办法当然有效,但它解决的是承载能力,不一定解决代码路径本身的低效问题。
如果你已经开始怀疑瓶颈在代码层,而不是单纯的资源不足,那么性能分析就值得尽快做。PHP 生态里常见的工具包括 xhprof、tideways、xdebug 等,而 XHProf 的优势在于接入相对直接,输出结果也足够适合做第一轮定位。
安装 XHProf 与运行依赖
文中以 PHP 7 为例安装 XHProf 扩展。先通过 Git 拉取源码,并切换到扩展目录:

git clone https://github.com/longxinH/xhprof.git ./xhprof
cd xhprof/extension/
如果你的环境是 php5.*,则需要使用另一个仓库:
https://github.com/phacility/xhprof.git
接着编译扩展,phpize 可以在 PHP 的安装目录中找到:
/usr/local/php/bin/phpize
./configure --with-php-config=/usr/local/php/bin/php-config
make && sudo make install
安装完成后,编辑 PHP 配置文件,在末尾追加:
[xhprof]
extension = xhprof.so
xhprof.output_dir = /tmp/xhprof
这里的 xhprof.output_dir 是性能分析文件的输出目录,PHP 运行用户必须对这个目录有写权限,例如:
chown -R www:www /tmp/xhprof
还有一个容易忽略的点:生成调用图时会用到 proc_open 函数,如果当前环境禁用了它,需要先开启。
修改完配置后,记得重启 php-fpm,然后用下面的命令确认扩展是否生效:
php -m |grep xhprof
除了扩展本身,还需要安装绘图依赖 graphviz,否则后面的调用图无法正常生成:
yum install libpng
yum install graphviz
如何运行并查看分析结果
在刚刚克隆下来的 XHProf 包中,自带了一个 example 示例。将整个 xhprof 目录放到一个站点目录下后,就可以先用官方示例验证环境是否跑通。

运行后,你会看到类似下面的输出:
Array
(
[foo==>bar] => Array
(
[ct] => 5
[wt] => 29
)
、、、、
)
---------------
Assuming you have set up the http based UI for
XHProf at some address, you can view run at
http://<xhprof-ui-address>/index.php?run=5b6116ddebeee&source=xhprof_foo
---------------
关键是复制其中的查询参数,比如 ?run=5b6116ddebeee&source=xhprof_foo,然后带到 XHProf 的 UI 页面中打开,对应的一次分析结果就能看到。
进入结果页后,点击 [View Full Callgraph],可以查看函数调用关系和各自运行时长。通常图中被标成红色的位置,就是耗时最明显的部分,排查和优化时可以优先盯住这些函数链路。
如果页面报错:
failed to execute cmd " dot -Tpng"
一般先检查两件事:一是 proc_open 是否可用,二是 graphviz 是否已经正确安装。
此外,直接访问下面这个入口,也可以查看全部分析记录:
http://您的域名/xhprof/xhprof_html/index.php
在自己的项目里接入 XHProf
如果你不只是想跑示例,而是要在自己的项目里做真实分析,可以参考 xhprof/examples/sample.php。核心思路并不复杂:把库文件带进项目,在程序入口开启分析,在请求结束时保存结果。
首先,把 xhprof/xhprof_lib 目录拷贝到自己的项目中。
然后在程序入口文件开头加入:
xhprof_disable();
在结尾加入保存分析结果的代码:
$XHPROF_ROOT = realpath(dirname(__FILE__) .'/..');
include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_lib.php";
include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_runs.php";
// save raw data for this profiler run using default
// implementation of iXHProfRuns.
$xhprof_runs = new XHProfRuns_Default();
// save the run under a namespace "xhprof_foo"
$run_id = $xhprof_runs->save_run($xhprof_data, "xhprof_foo");
程序运行一次后,就可以通过下面的地址查看结果:
http://您的域/xhprof/xhprof_html/index.php
实际使用时,更重要的不是“接入成功”本身,而是让分析发生在你真正怀疑有性能问题的请求上,例如首页、列表页、复杂报表接口或某些批处理逻辑。这样拿到的数据才更有参考价值。
结果字段怎么读,哪些指标最值得先看
XHProf 的结果页字段很多,但并不需要一上来全部吃透。先抓住“函数名、调用次数、总耗时、自身耗时、CPU、内存”这几组核心指标,就已经足够做第一轮判断。

Function Name:方法名称。
Calls:方法被调用的次数。
Calls%:方法调用次数在同级方法总数调用次数中所占的百分比。
Incl.Wall Time(microsec):方法执行花费的时间,包括子方法的执行时间。(单位:微秒)
IWall%:方法执行花费的时间百分比。
Excl. Wall Time(microsec):方法本身执行花费的时间,不包括子方法的执行时间。(单位:微秒)
EWall%:方法本身执行花费的时间百分比。
Incl. CPU(microsecs):方法执行花费的CPU时间,包括子方法的执行时间。(单位:微秒)
ICpu%:方法执行花费的CPU时间百分比。
Excl. CPU(microsec):方法本身执行花费的CPU时间,不包括子方法的执行时间。(单位:微秒)
ECPU%:方法本身执行花费的CPU时间百分比。
Incl.MemUse(bytes):方法执行占用的内存,包括子方法执行占用的内存。(单位:字节)
IMemUse%:方法执行占用的内存百分比。
Excl.MemUse(bytes):方法本身执行占用的内存,不包括子方法执行占用的内存。(单位:字节)
EMemUse%:方法本身执行占用的内存百分比。
Incl.PeakMemUse(bytes):Incl.MemUse峰值。(单位:字节)
IPeakMemUse%:Incl.MemUse峰值百分比。
Excl.PeakMemUse(bytes):Excl.MemUse峰值。单位:(字节)
EPeakMemUse%:Excl.PeakMemUse峰值百分比。
如果你只是想快速定位瓶颈,可以先这样理解:
先看总耗时,再区分是不是子调用拖慢
Incl.Wall Time 代表一个方法连同它调用的子方法一起,总共花了多少时间;Excl. Wall Time 则只看这个方法自身。前者高,说明这条链路整体慢;后者高,说明这个方法本身就重。
调用次数高,不一定是问题小
Calls 和 Calls% 可以帮助你识别“看似单次不慢,但被高频调用后累计很重”的函数。很多循环里的小函数、重复格式化、重复数据转换,都会在这里暴露出来。
CPU 和内存指标适合辅助判断瓶颈类型
如果 CPU 时间明显高,问题可能更偏计算或代码执行本身;如果 MemUse、PeakMemUse 明显异常,就要留意大数组、重复加载、对象堆积这类内存问题。
综合来看,XHProf 最有价值的地方,不是给出一个“优化建议”,而是把请求过程拆成可量化的函数链路,让你知道时间究竟消耗在了哪里。只有先找到真正的热点,再决定是改 SQL、减调用、做缓存,还是重构业务逻辑,优化才不会走偏。









