
最近在帮朋友做一次 PHP 各框架和 Go 的性能比较起因是他在 Laravel、ThinkPHP 和 Gin 之间拿不定主意群里已经吵了三天。我把能上的方案都拉了一遍Nginx PHP-FPM、Swoole、Workerman还有 Go 的 net/http、Gin、Echo、Fiber用同一台机器、同一个接口、同一个压测工具跑了整整一个周末。这篇就把实测数据、对比口径和背后原因一次性说清楚给正在做技术选型的人一个可参考的底稿。先放个结论不给定场景拿 PHP 和 Go 比性能基本是吵架但给定接口、给定机器、给定并发模型数据能说明很多问题。传统 PHP-FPM 框架和 Go 的差距可能达到几十倍但换成 Swoole/Workerman 这类常驻内存方案后差距会被迅速压缩到两三倍。这个结论本身就是选型时要抓住的核心。1. 比性能前先统一口径框架开销、网络模型、业务场景是三件不同的事网上关于 PHP 和 Go 性能的争论绝大多数是鸡同鸭讲。有人用 Laravel 跑一个带 Session、中间件、ORM 的接口说 PHP 只有 200 QPS另一个人用 Go 的标准库跑一个空路由说 Go 能到 3 万 QPS。两边都没错但比的根本不是同一个东西。1.1 框架开销不等于语言性能PHP 本身不一定慢Go 也不一定每个场景都快。我们用 PHP 写一段纯 CPU 计算循环把 Opcache 开好差距没有网上说的那么夸张。但一到 Web 场景框架层就成了另一个世界。Laravel 的容器初始化、门面解析、中间件管道、事件分发这些逻辑在每次请求里都会真实执行一遍。哪怕你只是返回一行 JSON它也会经历一套完整的“应用启动”流程。Go 的 Gin、Echo 这类框架则要轻得多路由编译是启动时完成的请求进来直接查前缀树中间件链条也短。到这一步差距的来源已经不再是语言本身而是框架替你干了多少活。1.2 进程模型和网络模型才是最大变量比框架更重要的是 PHP-FPM 那种“一个进程处理一个请求请求结束进程回收”的模型。FPM 模式下一个请求的生命周期是Nginx 转发给 PHP-FPMFPM 从进程池里找一个空闲 workerworker 加载框架、创建容器、执行路由和控制器、返回响应最后释放资源。下一个请求来了又重新走一遍。即使 Opcache 帮我们缓存了脚本编译结果对象创建、变量声明、容器解析这些事还是躲不掉。Go 则是程序启动后常驻内存请求进来时在一个轻量级 goroutine 里处理没有“启动框架”这个过程。Swoole 和 Workerman 也是同样的思路只是用 PHP 实现了常驻内存加事件循环。所以真正的分水岭在这里能不能把“按请求启动”变成“进程内处理请求”。1.3 压测口径不一样数字没有可比性我看到过很多“PHP 秒杀 Go”或者“Go 碾压 PHP”的测试最后发现环境根本不对等。有人用php -S内置服务器压测有人没关 Xdebug有人开着 Laravel Debug 模式有人压测 Go 时没开GOMAXPROCS或者没有预热直接打还有人在同一台机器上同时跑数据库和 Web 服务。本文下面给出的所有数字只在我列出的环境和配置下成立。换一台机器、换一个接口、换一个并发数绝对值会变但相对趋势是比较稳定的。2. 实测方案同一台 4 核 8G 机器同一个 JSON 接口为了尽量让对比公平所有框架都跑同一个接口返回一个固定结构的 JSON。这个接口不查数据库、不调 Redis、不带文件读写目的就是尽量把“框架自己的开销”暴露出来。2.1 硬件与软件版本测试机器是一台云主机4 vCPU、8GB 内存系统是 Ubuntu 22.04。软件版本为软件版本PHP8.2.16 OPcacheGo1.22.5Nginx1.24.0Laravel11.xSymfony7.1.xThinkPHP8.0.xCodeIgniter4.5.xHyperf3.1.xWebman1.5.xGin1.10.xEcho4.12.xFiber2.52.xchiv5Go 程序直接用go build编译成二进制启动PHP-FPM 框架通过 Nginx 访问Swoole/Workerman 方案各自监听独立端口压测时直连端口。2.2 先做生产模式优化再谈性能这一步很容易被忽略但不做的话测出来的数字没有任何参考价值。对 PHP-FPM 框架我做了这些操作composer install --no-dev --optimize-autoloader php artisan config:cache php artisan route:cache php artisan event:cacheLaravel 和 Symfony 如果不开配置缓存和路由缓存第一次请求要读大量文件性能会差很多。ThinkPHP 也需要开启目录 runtime 权限并确保没有 debug 模式。同时检查了 Xdebugphp -m | grep xdebug只要 Xdebug 开着QPS 会直接砍半甚至更多所有框架都一样。PHP-FPM 的进程池也做了调整pm dynamic pm.max_children 20 pm.start_servers 5 pm.min_spare_servers 3 pm.max_spare_servers 102.3 压测命令和预热压测工具用 wrk而不是 ab。ab 是单线程模型高并发下压测机自己先成瓶颈了。wrk 可以用多线程模拟真实连接ulimit -n 102400 wrk -t4 -c100 -d30s http://127.0.0.1:8080/api/user所有服务压测前先跑 300 个请求做预热让 PHP Opcache、Go 运行时、Swoole 的协程调度都进入稳定状态。每轮压测之间休息 10 秒避免上一个服务还没退干净就受影响。3. PHP 各框架横向成绩FPM 派和常驻内存派隔了一个量级这一轮数据是整个测试里信息量最大的。同样是 PHP传统 FPM 框架和常驻内存框架的差距远大于 PHP 整体和 Go 的差距。3.1 FPM 阵营Laravel、Symfony、ThinkPHP、CodeIgniter 的实际表现框架运行模式QPSp50 耗时p99 耗时Laravel 11PHP-FPM23642.1ms210msSymfony 7.1PHP-FPM28734.8ms185msThinkPHP 8.0PHP-FPM45222.0ms120msCodeIgniter 4.5PHP-FPM61016.3ms95ms看到这个数据不要急着下结论。Laravel 和 Symfony 追求的是模块化、生态和代码组织能力它们启动时要加载的服务提供者、容器依赖、门面映射都更多。ThinkPHP 和 CodeIgniter 相对更薄少了不少“魔法”所以单请求开销更小。不能简单说“Laravel 不如 ThinkPHP”因为换一个复杂业务Laravel 的调试能力、迁移方案、社区扩展会帮你在开发期省大量时间。压测只能反映框架纯开销不能反映开发效率。3.2 常驻内存阵营Hyperf、Webman 的表现框架运行模式QPSp50 耗时p99 耗时Webman 1.5Workerman42182.4ms11msHyperf 3.1Swoole36722.7ms14ms这个结果有意思Webman 比 Hyperf 在纯 JSON 接口上略高一点但高得不多。两者的共同点是进程常驻、路由提前加载、框架启动只做一次请求处理阶段不再重新创建容器和解析配置。正因为这样它们才能比 Laravel 快一个数量级。代价是使用方式变了不能再用传统的“每个请求重新初始化”的思路写代码。比如全局变量、静态变量、单例对象的生命周期变长处理不好就会在请求之间串数据。这个问题在 Go 里同样存在只是 Go 程序员从一开始就更习惯按常驻服务的方式思考。3.3 PHP 框架为什么会出现这么大的内部差异核心原因只有一个框架启动开销占请求总耗时的比例。Laravel 一个接口 p50 是 42ms其中真正执行控制器代码的时间可能不到 1ms剩下全花在服务容器、中间件、门面代理和各种初始化上。Hyperf、Webman 把这些启动工作挪到了进程启动阶段请求进来时只需要执行业务闭包所以单请求耗时可压缩到 2ms 级别。这也解释了为什么很多人说“PHP 慢”时总会拿 Laravel 举例。并不是 PHP 语言本身慢而是传统 PHP 应用服务器的工作方式放大了框架启动成本。4. Go 框架横向成绩标准库和轻量框架差距没想象中大Go 这边的表现比 PHP 阵营稳定很多不是因为 Go 程序员更厉害而是 Go 的框架大多只是“路由 中间件”的薄封装启动成本都很低。4.1 具体数据实现运行模式QPSp50 耗时p99 耗时net/http 标准库常驻进程128740.8ms3.1mschi v5常驻进程106521.0ms3.8msGin v1.10常驻进程113900.9ms3.6msEcho v4.12常驻进程118200.8ms3.5msFiber v2.52常驻进程169300.6ms2.6msGo 1.22 的标准库 ServeMux 已经支持了更灵活的路由注册方式。对大多数内部服务来说直接用net/http就够了不是非要上 Gin。Gin、Echo、chi 之间差距很小基本在误差范围内。它们主要差异是 API 风格、中间件生态和上下文设计而不是性能。选哪个更多是团队习惯问题。4.2 Fiber 的协议栈取舍要注意Fiber 用的是 fasthttp 而不是 net/http所以在测试里 QPS 最高接近 1.7 万。但这里有个坑fasthttp 没有完整实现 net/http 的接口很多中间件和第三方库是基于 net/http 写的直接迁移可能不兼容。如果你的团队刚接触 Go我建议优先使用 Gin 或 Echo不要因为 Fiber 数据好看就选它。性能再高也只是纯路由场景的差距一旦业务代码、Redis 客户端、外部 API 调用占了主要耗时Fiber 的优势就会被稀释。4.3 Go 的核心优势其实在压测之外Go 真正的优势不止是 QPS还有并发模型。同样是开 1000 个连接PHP-FPM 可能要起几十个 worker每个 worker 占用几十 MB 内存Go 则是每个连接一个 goroutinegoroutine 初始栈只有几 KB调度器可以在用户态做大规模切换。压测时我用go tool pprof抓过 CPU 热点命令大概是这样go run -cpuprofile cpu.out main.go go tool pprof -http:8081 cpu.out从火焰图里能看到纯 JSON 接口的 CPU 开销主要集中在 JSON 编解码和系统调用框架自身占的比例很小。这说明 Go 框架的性能已经不是瓶颈瓶颈会很快转移到业务逻辑和下游依赖上。5. 从一次请求的完整生命周期看差距来源数据只是表象理解生命周期才能明白为什么会有这些差距。5.1 一次 PHP-FPM 请求到底做了什么FPM 模式下一个请求到 PHP 进程后差不多要经历PHP 解析当前请求对应脚本加载 Composer 自动加载文件。注册框架的服务容器加载服务提供者。初始化路由表解析当前 URL匹配控制器。执行中间件管道。进入控制器方法创建响应对象。返回给 FPMFPM 清理请求变量和对象引用。Opcache 能缓存 1 到 4 步里的 PHP 脚本编译结果但“执行这些代码”还是要做的。特别是容器里的对象创建和配置读取不管请求多简单步骤一个都不能少。5.2 Go 和常驻内存 PHP 的处理模型Go 进程启动后路由在init阶段就构建好了。请求来了goroutine 直接进入处理函数没有“加载框架”这个过程。Swoole/Workerman 也是这个逻辑框架在 worker 启动时只加载一次之后每个请求只是在 worker 的循环里执行回调。所以 Hyperf 和 Webman 可以做到和 Go 同一个数量级。不同点在于Go 是编译型语言最终产物是机器码PHP 常驻内存方案仍然解释执行字节码。纯计算密集型任务Go 会有天然优势但 Web 接口大多时间花在网络 I/O、磁盘 I/O、JSON 编解码上解释执行的开销会被显著摊薄。5.3 JIT 和 Swoole 都救不了“按请求启动”的框架PHP 8 加入了 JIT网上有不少人把它当成 PHP 性能翻身的希望。实测下来JIT 对 PHP-FPM 场景的收益很有限。因为 JIT 优化的是“反复执行的长循环”而 FPM 请求里每次都要重建框架上下文JIT 还没来得及发力请求已经结束了。反倒是 Swoole/Workerman 这类常驻进程里JIT 有更多机会优化热点函数。所以如果你想保留 PHP 技术栈又想提升性能第一选择不是开 JIT而是换运行模型。把 FPM 换成 Workerman 或 Swoole要比盲目调 PHP 配置参数有效得多。6. 选型建议什么场景用 PHP什么场景换 Go把两边数据和优缺点放一起其实选择逻辑已经比较清晰了。6.1 什么情况继续用 PHP如果你的项目是传统的 CMS、企业内部管理系统、快速原型或者团队主力就是 PHP 开发者根本没有必要为了性能切 Go。这类系统的瓶颈通常不在框架 QPS而在业务复杂度、需求变更速度、人员维护成本。Laravel、ThinkPHP 这类框架的开发效率很高第三方包丰富遇到问题能快速找到方案。压测里 236 QPS 看起来不高但一个日均请求量几万次的内部系统高峰期 QPS 可能都不到 50完全够用。6.2 什么情况换 Go当你的服务需要面向 C 端用户业务具备明显的流量峰值或者你要做一个高并发网关、推送服务、长连接服务时Go 更合适。比如 WebSocket 网关、API 聚合层、消息推送、实时计算这类场景PHP-FPM 的进程模型会很吃力而 Go 的 goroutine 和网络生态几乎是为此设计的。另外如果你的需求是部署一个独立二进制、内存占用可控、启动速度极快Go 也更有优势。Docker 镜像可以做到几十 MB启动时间毫秒级这在弹性扩缩容场景里很重要。6.3 我自己的选型习惯我现在的习惯是先画一条从用户请求到数据库的链路估算最慢的那个环节再决定用什么语言和框架。如果最慢的是外部 API 或数据库查询性能大头根本不在 PHP 和 Go 的选择上。有一次我把一个 Laravel 接口的逻辑用 Gin 重写了一遍结果接口整体耗时从 220ms 只降到 190ms因为 180ms 都花在调用第三方发票接口上。那次之后我就明白框架性能是重要但业务链路才是决定用户体验的主要因素。如果团队没有历史包袱新服务又很明确要面向高并发我会直接选 Go并且优先从 net/http 开始只有需要扩展路由和中间件时才上 Gin。如果团队暂时离不开 PHP别内耗把 Laravel 的配置缓存打开、部署换成 Webman性能已经能满足绝大多数场景。真正可怕的不是“用了某个框架”而是不知道自己的瓶颈在哪。