你的接口响应慢,但不知道慢在哪里。你在代码里到处加 var_dump 和日志,结果还是找不到瓶颈。你需要一个能透视代码执行过程的工具——XHProf + XHGui,完全开源、自托管的 PHP 性能分析方案。

为什么选它
| 对比项 | XHProf + XHGui | Blackfire | New Relic |
|---|---|---|---|
| 价格 | 免费 | $199/月起 | $99/月起 |
| 数据存储 | 自己服务器 | Blackfire 云 | New Relic 云 |
| PHP 版本 | 5.x - 8.x | 7.0+ | 7.0+ |
| 函数级分析 | 支持 | 支持 | 支持 |
| 调用图可视化 | 支持 | 支持 | 支持 |
| 部署方式 | Docker / 手动 | SaaS / 自托管 | SaaS |
核心优势:免费、自托管、函数级精度、调用图可视化。数据不出服务器,适合对数据安全有要求的团队。
安装部署
第一步:安装 XHProf 扩展
XHProf 是 Facebook 开源的 PHP 性能分析扩展,能精确记录每个函数的执行时间、CPU 时间和内存占用。
# 从源码编译(支持 PHP 8.x)
cd /tmp
git clone --depth=1 https://github.com/longxinH/xh.git xhprof-src
cd xhprof-src/extension
phpize
./configure
make -j$(nproc)
sudo make install
# 创建配置文件
echo "extension=xhprof.so" | sudo tee /etc/php/8.4/cli/conf.d/20-xhprof.ini
echo "extension=xhprof.so" | sudo tee /etc/php/8.4/fpm/conf.d/20-xhprof.ini
# 验证安装
php -m | grep xhprof
# 输出:xhprof
注意:官方 phacility/xhprof 已停止维护,PHP 8.x 请用 longxinH/xh(fork 版本,持续维护)。
第二步:部署 XHGui
XHGui 是 XHProf 的 Web 可视化界面。不需要 MongoDB,可以直接用 SQLite:
# 克隆项目
cd /opt # 或你喜欢的目录
git clone --depth=1 https://github.com/perftools/xhgui.git
cd xhgui
# 安装依赖
composer install --no-dev
# 安装 PDO SQLite 驱动
sudo apt-get install -y php-sqlite3 # Debian/Ubuntu
第三步:配置存储
编辑 config/config.php:
<?php
return [
'save.handler' => 'pdo',
'pdo' => [
'dsn' => 'sqlite:' . __DIR__ . '/../data/xhgui.sqlite',
'user' => null,
'pass' => null,
'table' => 'results',
'tableWatch' => 'watches',
'initSchema' => 'true',
],
'timezone' => 'Asia/Shanghai',
];
创建数据目录并初始化:
mkdir -p data
touch data/xhgui.sqlite
首次访问时 XHGui 会自动创建表结构。
第四步:启动服务
cd /opt/xhgui
# 方式一:开发测试(PHP 内置服务器)
php -S localhost:8180 -t webroot/
# 方式二:生产环境(Nginx + PHP-FPM)
# Nginx 配置 webroot 指向 /opt/xhgui/webroot/
打开浏览器访问 http://localhost:8180,看到空白的运行列表就说明部署成功。
接入你的项目
最简接入(3行代码)
在你的应用入口文件添加:
<?php
// public/index.php 或入口文件最前面
xhprof_enable(XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY);
register_shutdown_function(function () {
$data = xhprof_disable();
// 保存到 XHGui
require '/opt/xhgui/src/autoload.php';
$app = new \XHGui\Application();
$saver = $app->getSaver();
$now = microtime(true);
$saver->save([
'meta' => [
'url' => $_SERVER['REQUEST_URI'] ?? '/cli',
'SERVER' => $_SERVER,
'get' => $_GET,
'env' => $_ENV,
'simple_url' => preg_replace('#\?.*$#', '', $_SERVER['REQUEST_URI'] ?? '/cli'),
'request_ts_micro' => [
'sec' => (int)$now,
'usec' => ($now - (int)$now) * 1000000,
],
],
'profile' => $data,
]);
});
// 你的框架启动代码...
$app = run();
生产环境采样
不要每个请求都采集,采样即可:
// 只采集 1% 的请求(生产环境推荐)
if (rand(1, 100) === 1) {
xhprof_enable(XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY);
register_shutdown_function(function () {
// ... 同上
});
}
采样模式下性能开销 < 5%,几乎无感知。
框架集成示例
ThinkPHP:
// app/ExceptionHandle.php 或 public/index.php 入口处
if (env('APP_DEBUG') || rand(1, 100) === 1) {
xhprof_enable(XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY);
register_shutdown_function(function () {
$data = xhprof_disable();
if (!$data) return;
require '/opt/xhgui/src/autoload.php';
$app = new \XHGui\Application();
$saver = $app->getSaver();
$now = microtime(true);
$saver->save([
'meta' => [
'url' => $_SERVER['REQUEST_URI'] ?? '/cli',
'SERVER' => $_SERVER,
'get' => $_GET,
'env' => $_ENV,
'simple_url' => preg_replace('#\?.*$#', '', $_SERVER['REQUEST_URI'] ?? '/cli'),
'request_ts_micro' => ['sec' => (int)$now, 'usec' => ($now - (int)$now) * 1000000],
],
'profile' => $data,
]);
});
}
Laravel:
// app/Providers/AppServiceProvider.php boot() 方法
if (config('app.env') !== 'production' || rand(1, 100) === 1) {
xhprof_enable(XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY);
register_shutdown_function(function () {
// 同上
});
}
使用指南
运行列表
访问 XHGui 首页,你会看到所有已记录的请求:

每条记录显示:
- URL — 请求地址
- Time — 发生时间
- wt — 壁钟时间(Wall Time,实际耗时)
- cpu — CPU 时间
- mu — 内存使用量
- pmu — 峰值内存
顶部导航栏提供多种排序方式:
- Recent — 最近记录
- Longest wall time — 最耗时请求
- Most CPU — CPU 密集型请求
- Most memory — 内存密集型请求
- Custom View — 自定义筛选
- Watch Functions — 监控指定函数
- Waterfall — 瀑布图视图
详情分析
点击任意一条记录进入详情页:

左侧显示本次运行的摘要信息:
- URL、时间戳、ID
- Wall Time / CPU Time / Memory Usage / Peak Memory
- GET 参数、SERVER 信息
右侧是两个关键图表:
Self Wall Time(自身耗时排行)
- 显示每个函数自身的耗时(不含子调用)
- 图表越长表示该函数越耗时
- 快速定位性能热点
Memory Hogs(内存消耗排行)
- 显示每个函数的内存分配量
- 发现内存泄漏嫌疑
点击 Jump to functions 查看完整的函数调用列表,包含每个函数的调用次数、耗时、内存等详细数据。
调用图
点击 View Callgraph 查看函数调用关系图:

调用图直观展示:
- 函数之间的调用关系(谁调用了谁)
- 每个函数的耗时占比(节点大小)
- 调用次数(箭头标注)
- 瓶颈一目了然(最大的红色节点)
提示:占比低于 1% 的函数会被自动省略,保持图表清晰。
实战案例
案例1:定位慢接口
场景:/api/orders 接口平均响应 2 秒
接入 XHProf 后,在详情页发现:
Self Wall Time 排行:
1. DB::query() - 1,800ms ← 瓶颈!
2. OrderService::getOrders() - 1,850ms
3. UserController::auth() - 80ms
4. View::render() - 70ms
原因:orders 表缺少索引,全表扫描 10 万条记录。
修复:
ALTER TABLE orders ADD INDEX idx_user_created (user_id, created_at);
效果:1,800ms → 15ms,提升 99%。
案例2:发现内存泄漏
场景:后台任务运行后内存持续增长
Memory Hogs 排行显示:
1. ImageProcessor::resize() - 15MB × 50次调用
2. FontLoader::loadFont() - 12MB × 50次调用
3. Template::render() - 2MB
问题代码:
class ImageProcessor {
private static $cache = []; // 静态数组永不释放
public function resize($image) {
self::$cache[] = $image; // 每次调用都追加!
return $this->doResize($image);
}
}
修复:
public function resize($image) {
$result = $this->doResize($image);
unset($image); // 及时释放
return $result;
}
案例3:优化第三方库
场景:PDF 生成接口很慢
调用图显示:
main() wt=3200ms
└─ PDF::generate() wt=3000ms
└─ Font::load() wt=2500ms ← 每次都重新加载字体!
修复:加缓存
class Font {
private static $fonts = [];
public static function load($name) {
if (!isset(self::$fonts[$name])) {
self::$fonts[$name] = self::loadFromFile($name);
}
return self::$fonts[$name];
}
}
效果:3,000ms → 500ms。
最佳实践
1. 采样策略
// 开发环境:全量采集
xhprof_enable();
// 测试环境:10% 采样
if (rand(1, 10) === 1) { xhprof_enable(); }
// 生产环境:1% 采样
if (rand(1, 100) === 1) { xhprof_enable(); }
// 高流量:0.1% 采样
if (rand(1, 1000) === 1) { xhprof_enable(); }
2. 忽略噪音
只关注业务代码,过滤内置函数:
xhprof_enable(
XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY | XHPROF_FLAGS_NO_BUILTINS
);
// 或者只监控 API 请求
if (strpos($_SERVER['REQUEST_URI'], '/api/') === 0) {
xhprof_enable();
}
3. Watch Functions(监控关键函数)
在 XHGui 界面设置 Watch Functions,当某个函数超过阈值时自动标记:
// config/config.php 中配置
return [
// ... 其他配置
'watch_functions' => [
['name' => 'DB::query', 'threshold' => 100], // 超过100ms告警
['name' => 'curl_exec', 'threshold' => 500], // 超过500ms告警
['name' => 'file_get_contents', 'threshold' => 200],
],
];
4. 定期清理数据
SQLite 文件会持续增长,建议定期清理:
# crontab 每周清理30天前的数据
0 3 * * 0 find /opt/xhgui/data -name "*.sqlite" -exec sqlite3 {} "DELETE FROM results WHERE request_date < date('now', '-30 days');" \;
或者直接删除重建:
# 更简单的方式:每月重置
0 3 1 * * rm -f /opt/xhgui/data/xhgui.sqlite && touch /opt/xhgui/data/xhgui.sqlite
存储方案对比
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| SQLite | 个人/小团队 | 零配置、无需额外服务 | 不支持分布式写入 |
| MySQL | 中型团队 | 成熟稳定 | 需要额外数据库实例 |
| MongoDB | 大流量 | 写入性能高 | 资源占用大 |
本文使用 SQLite 方案,零依赖,适合大多数场景。如果需要切换到 MySQL 或 MongoDB,只需修改 config/config.php 中的 save.handler 和对应连接配置即可,代码不用改。
常见问题
Q: XHProf 支持 PHP 8 吗?
支持。但要用兼容版本:
# 官方版(phacility)已停更,不支持 PHP 8
# 用 fork 版本:
git clone https://github.com/longxinH/xh.git
Q: 性能开销大吗?
很小。采样模式下:
| 模式 | 开销 |
|---|---|
| 全量采集 | ~10-15% |
| 1% 采样 | < 1% |
| 0.1% 采样 | < 0.1% |
生产环境推荐 1% 采样,几乎无感知。
Q: 能监控 CLI 脚本吗?
可以。在脚本开头加同样的代码即可:
#!/usr/bin/env php
<?php
xhprof_enable(XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY);
// 你的脚本逻辑...
$data = xhprof_disable();
// 保存到 XHGui(同上)
Q: 和 XDebug 冲突吗?
不冲突。两者用途不同:
- XDebug — 断点调试、堆栈跟踪
- XHProf — 性能分析、函数级耗时统计
可以同时启用,但不建议在生产环境同时开启(都有一定开销)。
Q: 数据存哪里?
取决于你的配置:
# SQLite(本文方案)
/opt/xhgui/data/xhgui.sqlite
# MySQL
mysql> SELECT * FROM xhprof.results LIMIT 1;
# MongoDB
mongo> use xhprof; db.results.find().pretty()
总结
XHProf + XHGui 让 PHP 性能问题无处遁形:
- 免费开源,零成本
- 自托管,数据安全
- 函数级精度分析
- 调用图可视化
- 支持所有主流 PHP 框架
- SQLite 零配置起步
快速上手清单:
- 编译安装 XHProf 扩展(5 分钟)
- 克隆 XHGui 并
composer install(3 分钟) - 配置 SQLite 存储(1 分钟)
- 入口文件加 3 行代码(1 分钟)
- 打开浏览器查看第一个性能报告
本文基于 XHProf v2.3 (longxinH/xh) + XHGui (perftools/xhgui) + PHP 8.4 实测编写,所有代码均通过验证。
原文标题: XHProf + XHGui:搭建PHP性能分析系统
原文地址: https://phpreturn.com/index/a6a1686c74d5e2.html
原文平台: PHP武器库
版权声明: 本文由phpreturn.com(PHP武器库官网)原创和首发,所有权利归phpreturn(PHP武器库)所有,本站允许任何形式的转载/引用文章,但必须同时注明出处。