"PHP 各框架下和 Go 的性能比较"这个话题,我在技术群里见过太多次了。每次一有人抛出来,评论区基本就会分成两派:一边说 PHP 该淘汰了,一边说业务跑得好好的换什么换。而绝大多数争论都停留在口号层面,没有人把"具体哪个框架、哪种业务场景、什么压测条件"讲清楚,所以谁也说服不了谁。
我自己是两种技术栈都在用的状态:PHP 写了十几年,线上一直有 Laravel 和 ThinkPHP 项目在维护,最近几年把一部分高并发管道业务切到了 Go。这篇文章不是来当裁判的,而是想把我在同一套环境下跑过的性能测试、遇到过的瓶颈、以及在几个真实业务场景里的选型思考完整记录下来。如果你正在纠结 Laravel 还是 Gin、Hyperf 能不能顶替 Go,或者单纯想弄明白"PHP 慢到底慢在哪",这篇应该能给你一个相对完整的参考。
1. 先泼盆冷水:"PHP慢、Go快"这句口诀经不起细问
1.1 同样叫"PHP",性能上下能差几十倍
很多人说"PHP 慢",但实际指的是"PHP-FPM 跑传统 MVC 框架"这个特定组合。同样是 PHP,Laravel 和 Hyperf 在同一个接口上的 QPS 差距可能达到十倍甚至更多。Laravel 慢不代表 PHP 语言本身慢,Hyperf 就是 PHP 写出来的应用框架,但它采用了 Swoole 常驻内存和协程模型,性能表现完全是另一个量级。
反过来,Go 也有写得很烂的代码,GORM 全表查询加循环 N+1,调优之前照样能把服务拖垮。跨语言谈性能,必须先明白一个前提:框架、运行模式、代码写法的影响,往往比语言本身更大。我见过最典型的例子,是把 Laravel 的 Eloquent 用在循环里逐条读写,200 行代码把接口拖到 5 秒以上;换成批量查询加原生 SQL 后,响应时间直接降到 80ms,全程没有换语言。
1.2 语言执行只是端到端耗时的一小块
我见过很多团队把"PHP 慢"挂在嘴边,可真正把慢接口拉出来看时,发现 80% 以上的时间花在数据库 SQL、Redis 网络往返、第三方 API 调用上,语言自身的执行时间可能只有百分之几。一个接口总耗时 200ms,其中 PHP 脚本真正执行计算的时间可能只有 2ms,剩下的基本都在等 IO。
等 IO 这件事,Go 的协程确实比 PHP-FPM 的进程模型更擅长,但如果你只是想把 200ms 降到 195ms,从语言层面下手是性价比很低的方案。先把慢 SQL 修掉,把 N+1 查询消灭掉,把没必要的远程调用缓存起来,这些优化带来的收益要明显得多。这也是为什么很多"换 Go 之后性能飞升"的故事,细问下来其实是"重写的时候顺手把烂 SQL 优化掉了",而不是 Go 本身有多神。
2. 同场竞技:PHP 主流框架与 Go 主流框架的实测数据
2.1 测试环境与压测口径
先说明一下我的测试环境:4 核 8G 云主机,PHP 8.2 开了 OpCache,Go 1.22,都用 Docker 部署在同一台机器上避免网络差异。压测工具用的 wrk,固定 200 并发、持续 60 秒。
为了尽量公平,PHP 侧 Laravel 11、ThinkPHP 8、Symfony 7 都保持默认配置,不做过度的性能裁剪,Hyperf 3 按官方默认配置用 Swoole 运行;Go 侧选了 Gin、Echo、标准库 net/http 三组。测试接口分三种:纯 JSON 响应、单表 MySQL 查询后返回 JSON、Redis 读取后返回 JSON。
这里必须强调,这些数字是我这台机器上的相对趋势,不是框架官方标准。换到不同硬件、不同 PHP/Go 版本、不同依赖环境下数字一定会有波动,但相互之间的量级关系有参考价值。另外,PHP 侧默认没有开 JIT,开了 JIT 对纯 CPU 计算有帮助,但对 Web 请求中占比很大的框架启动和 IO 等待,提升并不明显。
2.2 纯 JSON 接口:框架厚度立刻拉开差距
最简单的纯 JSON 接口最能反映框架本身的"启动成本"。实测下来,Laravel 11 大致在 700 到 900 QPS 之间,ThinkPHP 8 能到 1500 QPS 左右,Symfony 7 在 900 到 1100 QPS 之间;到了常驻内存的 Hyperf 3,能跑到 8000 QPS 以上。Go 这边,Gin 在 2 万 QPS 上下,Echo 略低一点,标准库 net/http 大概 2.5 万到 3 万 QPS。
Laravel 和 Gin 之间接近三十倍的差距,看起来吓人,但拆开看主要来自两件事:一是 PHP-FPM 每请求都重建环境,二是 Laravel 本身就是"厚"框架,服务容器、门面、中间件管道、事件系统在每次请求里都要完整走一遍。Gin 的路由用压缩前缀树,中间件只是链式函数调用,JSON 序列化走标准库,启动成本被压到了极低。
2.3 加入 MySQL 和 Redis 后:差距快速收窄
为了贴近真实业务,第二个接口做了单表查询并按 id 返回一条用户记录。这时候差异非常有意思:Laravel 降到 300 左右,ThinkPHP 400 多,Symfony 350 上下,Hyperf 能到 1500 上下;Gin + GORM 大约 1800 到 2200 QPS。数据库访问成了新的瓶颈,PHP 和 Go 的差距从三十倍缩小到六倍以内。
第三个接口读 Redis 再返回 JSON,Laravel 在 600 左右,Hyperf 能到 3500 左右,Gin 大概 6000 到 8000。核心原因很简单:当请求时间里有大量等待交给数据库、Redis 时,语言执行能力对整体 QPS 的影响被稀释了,IO 等待和连接池吞吐才是大头。
2.4 关键指标速览
| 测试项 | Laravel 11 | ThinkPHP 8 | Symfony 7 | Hyperf 3 | Go Gin |
|---|---|---|---|---|---|
| 纯 JSON | 约 800 | 约 1500 | 约 1000 | 约 8000+ | 约 20000+ |
| MySQL 单查 + JSON | 约 300 | 约 450 | 约 350 | 约 1500 | 约 2000 |
| Redis 读取 + JSON | 约 600 | 约 1000 | 约 700 | 约 3500 | 约 7000 |
看这张表,能得出几个实际有用的结论:如果你的系统绝大多数接口都要查数据库、调缓存,那么 Laravel 和 Gin 的 QPS 差距没有传说中那么吓人;如果你的系统有一些纯计算、纯转发的接口,PHP-FPM 的劣势会暴露得非常明显。这也是为什么网关、短链服务、推送服务这类偏"薄"的场景,Go 几乎是无脑选择。
3. 差异的根子:请求生命周期、并发模型与框架厚度
3.1 PHP-FPM 的"用完即焚"模型
PHP-FPM 下每个请求基本是无状态的:Web 服务器收到请求,FPM 分配一个进程(或复用已有进程),PHP 从头开始加载框架代码、注册服务、解析路由、执行控制器,响应完之后把进程里的大部分资源释放掉。相当于每次来一桌客人,都要把餐厅拆了重新装修一遍。框架本身越大,启动开销越明显,这就是 Laravel 在纯 JSON 接口上 QPS 上不去的最直接原因。
OpCache 能缓存编译后的字节码,但类加载、服务注册、对象构造这些开销依然每次请求都要发生。这也是 PHP 从设计上就和常驻型服务有差距的地方,但注意这个差距属于"请求模型",不等于"PHP 语言不行"。PHP 官方这些年一直在推动 JIT,但 Web 场景下收益有限,因为瓶颈不在纯计算,而是每次请求的生命周期太短,还没来得及把热点代码跑热,请求就结束了。
3.2 Go goroutine 的并发模型
Go 的并发模型是另一个方向:进程启动后把所有代码加载进内存,每个请求只需要创建一个 goroutine。goroutine 初始栈很小,几 KB 起步,一个进程轻松承载上万个并发连接;配合 epoll 网络轮询,读写请求大多数时候在等待网络数据时并不占用线程。
用大白话说,Go 的服务是"一个人同时接待很多客人",而 PHP-FPM 是"一个服务员只服务一桌客人",前者在并发场景下当然更省资源、吞吐更高。这是 Go 在高并发网关和推送场景优势的本质来源,也是 PHP 社区里 Swoole、RoadRunner 这些项目拼命想补齐的能力。RoadRunner 的做法更有意思,它本身是 Go 写的应用服务器,PHP 只负责处理业务逻辑,请求生命周期被 RoadRunner 接管,也能显著缩小和 Go 的距离。
3.3 框架厚度:Laravel 为什么"重",Gin 为什么"薄"
同样是 Go 框架,Gin 之所以快,除了语言本身,还因为它足够薄:路由用的是压缩前缀树,中间件只是链式函数调用,JSON 序列化走标准库。Laravel 强大,是因为它把开发者需要的东西都塞进了请求生命周期里:服务容器、门面、Eloquent ORM、事件系统、队列、认证、授权等,每一样都有成本。
ThinkPHP 比 Laravel 轻一个量级,因为它的服务注册和对象管理更简单,纯 JSON 接口 QPS 高出快一倍也印证了这一点。所以框架选型本质上是在"启动开销"和"开发生产力"之间做交换,没有一个框架在所有维度上都占便宜。你在 Laravel 里享受的每一个省心的语法糖,都在为接口延迟买单,只不过大多数业务系统根本感知不到这几毫秒的差别。
3.4 常驻内存 PHP:Swoole/Hyperf 如何缩小差距
Swoole 让 PHP 也能常驻内存,配合协程实现类似 Go 的并发模型。Hyperf 就是基于 Swoole 构建的全栈框架,代码常驻、对象复用、数据库连接池、协程调度,所以它的纯 JSON 接口能跑到 8000+ QPS,已经和低配 Go 服务处于同一个数量级。
代价是引入了一层需要专门运维的常驻进程服务,代码里要时刻注意内存泄漏、协程安全、连接复用,开发心智成本比传统 PHP-FPM 高不少。我用 Hyperf 做过消息推送服务,稳定性没有问题,但团队里如果有人依然拿 PHP-FPM 的思路写 Hyperf,很快会踩到内存泄漏的坑。想用 PHP 追平 Go 的并发能力,Swoole 系确实是唯一能打的路,只是这条路的复杂度并不会比直接学 Go 低多少,选哪条得看团队底子。
4. 用 xhprof 和 pprof 把"慢"定位到函数级
4.1 PHP 侧:从 xhprof 到 Tideways,看框架启动开销
如果只是嘴上说"Laravel 慢",很多人是不服的,所以我建议任何优化都先做函数级性能分析。老牌的 xhprof 在 PHP 7 之后兼容性一般,我这边更喜欢用 tideways_xhprof,它是 xhprof 的现代分支,基本支持 PHP 8。安装方式也很简单:
pecl install tideways_xhprof在 php.ini 里加上 extension=tideways_xhprof.so,然后在你想要分析的入口开启:
\Tideways\Profiler::start([ 'collect_memory' => true, 'collect_malloc' => true, ]); // 业务代码... \Tideways\Profiler::stop();跑完能输出火焰图和函数耗时列表。我在 Laravel 项目上做过一次分析,结果非常典型:一个简单的控制器,真正执行业务逻辑的时间只占整个请求的三成左右,剩下的大部分都耗在 Composer 自动加载、ServiceProvider 注册、门面解析、路由匹配和中间件管道上。这就是框架厚度的直接证据。
4.2 Go 侧:go tool pprof 的使用逻辑
Go 这边的性能分析工具链成熟很多。要在 Web 服务里打开 pprof,只需要引入标准库的接口:
import _ "net/http/pprof"如果服务框架接管了默认端口,可以单独起一个 goroutine 对外暴露 pprof 端口:
go func() { http.ListenAndServe(":6060", nil) }()要采集 CPU 30 秒的 profile,命令是这样的:
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30进入交互界面后,输入 top 能看到耗时最高的函数,输入 web 能生成火焰图并在浏览器打开。常见瓶颈也很套路化:JSON 序列化、正则匹配、反射调用、数据库连接等待。有一点要注意,线上开 pprof 会对性能有一点影响,一般建议只在压测机或低流量时段开短时间的 profile,不要让它常驻生产。
4.3 案例:同一个 Redis 消费组,PHP 和 Go 的差距在哪
我拿 Redis 消费组场景做个实测案例,这也是很多团队从 PHP 迁 Go 的第一个点。PHP 侧我写了一个 CLI 常驻脚本,用 XREADGROUP 阻塞读取新消息,逐条处理;Go 侧用同样的逻辑,但开了 20 个 worker goroutine 并发处理,外层用 channel 分发。测试数据是本机 Redis 里的 10 万条 JSON 消息,处理内容只是解析 JSON、累加几个字段再写回结果集。
PHP 单进程大概跑到每秒 3000 到 5000 条,Go 跑到了每秒 4 万条以上。这个差距来自两层:一是 Go 的并发拆分了 IO 等待,Redis 往返延迟被多个 worker 分摊;二是同等逻辑下,Go 处理 JSON 解析的 CPU 效率也明显更高。如果你现在也是把 PHP 当队列消费者用,换成 Go 或者至少换成 Swoole 常驻进程,收益都非常明显。这也是热词里"php redis 消费组"背后最常见的真实需求——不是 PHP 做不了,而是它用进程模型做这件事,成本太高。
5. 场景决定选型:别让基准测试骗了你
5.1 CPU 密集场景:图片处理、视频压缩,Go 有优势但不万能
热词里能看到不少人搜 PHP 图片生产、PHP 视频压缩。PHP 用 GD、Imagick 做图片处理,简单场景没问题,但到了批量缩略图、视频抽帧、压缩转码这类 CPU 密集型任务,PHP 的执行效率明显不如 Go。Go 有 imaging、ffmpeg-go 这类库,性能表现好不少。
但说句实话,真正的重计算——批量视频转码、大量图像滤镜叠加——无论 PHP 还是 Go,都不如 C/C++/Rust 或者直接调 ffmpeg 命令行。比较合理的设计是:业务系统用 PHP 接收任务、管理队列,把真正的计算放到独立 Worker 里,Worker 可以用 Go 写,也可以直接调系统级的 ffmpeg 进程。性能优化不一定是换语言的动机,把计算从 Web 请求链路里拆出去,往往比换语言更便宜、更见效。
5.2 IO 密集与高并发:网关、推送、长连接,Go 几乎是无脑选择
WebSocket 长连接、推送网关、API 网关这类 IO 密集型服务,对 PHP-FPM 来说是结构性的不匹配。FPM 一个进程同时只能服务一个请求,长连接会把进程全部占满;Swoole 能靠协程扛几万连接,Go 的 goroutine 模型能更轻松地扛到几十万连接级别。
同样是做秒级推送,我见过用 Go 写的网关单机支撑几十万在线连接,这在 PHP-FPM 下基本是不可想象的。如果你的核心业务场景是这些,别犹豫,用 Go。但要注意,Go 的高并发是有代价的,goroutine 泄漏、channel 阻塞、内存增长这类问题排查起来比 PHP 的"请求结束就释放"要复杂得多,团队没有相应基础,上线后运维成本会直接拉高。这也是为什么有些团队迁到 Go 之后,反而觉得比 PHP 更难维护,性能上去了,人的压力也上去了。
5.3 复杂业务后台:为什么 PHP 依旧能打
很多后台系统、CRM、运营管理平台、中后台电商管理,核心瓶颈根本不是 QPS,而是业务复杂度、权限模型、工作流状态机、报表 SQL 这种开发效率和灵活性问题。这类系统的 QPS 需求可能只要几百,但功能清单长到吓人。Laravel 的 Eloquent、队列、事件、认证授权、社区生态包,能让一个中等水平的团队在很短的时间内搭建出功能丰满的系统。
我用 Go 写过类似的复杂后台,最后的感受是:业务逻辑本身不复杂,但数据模型映射、枚举状态流转、动态查询条件拼装,这些在 PHP/Java 里很顺手的事,在 Go 里写得又长又容易出错。所以在这个赛道里,"PHP 慢"根本不是一个有效的反对理由,因为系统的瓶颈从来不在语言上。你要是硬把一个需求复杂、迭代频繁的后台重写成 Go,大概率会得到一套性能没有本质提升、但开发速度明显变慢的系统。
6. 从 PHP 转 Go 的隐藏成本与混合架构经验
6.1 隐藏成本:不只是重写代码那么简单
性能不是你迁移技术栈的唯一成本。团队学习曲线是最容易被低估的一项:PHP 里"一切皆数组"的开发体验,转到 Go 之后会立刻感受到静态类型和手动错误处理的繁琐。拿最常见的数据结构来说,PHP 一个关联数组能装下所有东西,Go 里你得定义一个 struct,还要考虑它是否实现了某个接口。
再加上 composer 生态里大量成熟的库在 Go 里要么没有、要么需要自己封装,比如支付、物流、ERP 对接这类业务包,PHP 社区基本开箱即用,Go 社区则常常需要自己花时间补齐。如果只是"为了性能"而全面重写一个功能复杂的业务系统,大概率会在重写过程中发现性能根本不是主要矛盾,反而把业务开发节奏拖垮了。我见过不止一个团队,用三个月重写旧系统,半年后还在一遍遍对功能,上线时间一拖再拖。
6.2 什么业务值得迁,什么业务留着
我自己的判断标准很简单:先看系统的性能瓶颈是不是出在语言执行层面。如果瓶颈在数据库慢查询、业务逻辑复杂度和功能迭代速度上,换语言解决不了问题;如果瓶颈是单机高并发转发、海量长连接、CPU 密集计算,而且这些场景可以独立成服务,那就有充分的理由用 Go 从零构建,而不是把老业务全部推倒。
换句话说,做增量迁移,不做整体推翻。把新服务用 Go 写在系统边缘,把老 PHP 业务留在中心,是风险和收益平衡得最好的姿势。比如对接外部系统的网关、实时数据处理管道、WebSocket 推送服务,这些边界清晰、依赖少的模块最适合先切过去;而核心业务、后台管理、订单流程这类和数据库绑定深、逻辑联动多的部分,留在 PHP 里反而是更好的选择。
6.3 我的混合架构实践
我现在的线上架构,对外入口是 Nginx 加一层 Go 写的 API 网关,负责统一鉴权、限流、灰度路由;网关后面接了 PHP 的 Laravel 后台业务系统和 Go 的高并发任务管道,两者通过 Redis 和消息队列通信。高并发、低延迟、纯转发的接口走到 Go;复杂业务逻辑、后台管理、报表、工作流留在 PHP。
跑了一年多,这个架构的稳定性出乎意料地好,PHP 团队不用学习 Go,Go 组也不需要深入 PHP 业务细节,两边通过接口契约协作,冲突很少。对我来说,技术选型的答案从来不是"PHP 还是 Go",而是"这个流量和复杂度下,哪个组合的方式最省事"。性能数字只是决策依据里的一列,不是全部。
最后再说一个我自己养成的习惯:任何技术选型之前,先拿真实流量的一小部分做一次最小压测,再让团队一起看看这段逻辑在两种技术栈下的可维护性,最后再投票。语言之争背后,真正决定项目成败的往往不是并发模型这个单一因子,团队熟悉度、生态覆盖度、出问题时的排查效率,这些东西不会出现在 QPS 数字里,但会在每一次上线和故障处理时反复出现。这套方法论,比记住我上面任何一个表格里的数字都更值得带走。