Laravel 使用 PHP TrueAsync 协程 I/O 性能实测 性能爆表啦
把 Laravel 请求放进协程里处理,性能到底能提升多少?
是 30%、50%、200%,还是 400%?
Laravel Octane 已经让 Laravel 适配了有状态运行模式,使 Laravel 进程可以常驻内存运行,从而跳过每次请求重复的框架启动流程。
但是,几乎没人真正尝试过让 Laravel 变成一个“异步框架”。
过去,社区里一种常见判断是:
让 Laravel 异步化实在太难了。
然而,TrueAsync 0.6.0 发布后,爱好者 YanGusik 在没有大幅改动代码的情况下,成功适配了 Laravel 的核心部分。
这真的可能吗?
听起来像某种技巧。毕竟,很多人曾尝试让 Laravel 适配 Swoole 协程,最后诞生的是一个从零构建的新框架 —— Hypervel。
Hypervel 采用等价 API,重新实现了一套框架代码。
那么,这一次又是怎么做到的?
一、性能基准测试
测试环境
本次测试环境如下:
- OS:WSL2,Linux 5.15
- CPU:16 核
- 内存:7.8 GB RAM
- 数据库:PostgreSQL 16,
max_connections=500 - 压测工具:k6
- 压测模式:
constant-arrival-rate - 目标请求量:1000 req/s
- 持续时间:30 秒

说明:这些测试使用的是未开启协程模式的 Swoole,因为 Laravel 尚未适配 Swoole 协程模式。 在纯合成协程测试中,Swoole 的表现略优于 FrankenPHP + TrueAsync。两者性能接近,在 12 个 worker 下都能达到约 10,000 req/s。
测试项目地址:
https://github.com/YanGusik/ta_benchmark
二、测试工作负载
本次测试并不是简单的 “Hello World” 合成测试,而是更贴近真实 Web 应用场景的数据库 I/O 工作负载。
/bench 端点会对 PostgreSQL 顺序执行 10 条 SQL 查询,包括:
- 用户查询
- 文章列表查询
- 插入浏览记录
- 更新浏览计数
- 聚合查询
- TOP-N 查询
数据库中包含:
- 100 个用户
- 1,000 篇文章
- 持续增长的
post_views表
也就是说,这个测试重点考察的不是 PHP 空跑性能,而是典型 Web 应用中更常见的 数据库 I/O 密集型场景。
三、吞吐量测试:目标 1,000 req/s

在 16 个 worker 下:
- TrueAsync:987 req/s
- Laravel Octane + Swoole ZTS:601 req/s
TrueAsync 几乎达到了 1,000 req/s 的目标。
而 Laravel Octane 的最佳结果为 601 req/s,在相同 worker 数量下,吞吐量比 TrueAsync 低约 64%。
这里使用 16 个 worker,是为了给阻塞式服务器提供更有利的测试条件。
更关键的是:
FrankenPHP + TrueAsync 只需要 4 个 worker,就能像 16 个 worker 一样稳定处理每秒 1,000 个请求。
原因在于,协程会在每次 PDO::query() 时让出控制权。
当一个协程正在等待数据库响应时,当前 worker 并不会空闲等待,而是可以继续执行其他协程中的请求。
也就是说:
单个 worker 可以同时处理数十个请求。
阻塞式服务器则不同。它要达到接近 990 req/s 的吞吐量,需要更多 worker 来堆并发。

四、中位延迟 P50:差距达到 56 倍

在 16 个 worker 下:
- TrueAsync:29 ms
- Swoole Laravel Octane:1,640 ms
两者的 P50 延迟差距达到 56 倍。
这些秒级延迟几乎都来自队列等待时间。

需要注意的是,PHP 代码和 SQL 的实际执行速度并没有本质差异。
真正的差异来自服务器模型:
阻塞式服务器必须等待当前请求完成后,才能开始处理下一个请求。 协程服务器则可以在当前请求等待 I/O 时,继续处理其他请求。
换句话说,阻塞式模型中的进程或线程,大量时间都花在“等待”上。
TrueAsync 的 CPU 利用率更高,是因为协程之间不会互相阻塞。它们在等待 I/O 时主动让出控制权。
这里没有魔法,只有更高效的资源利用方式。
五、负载下的内存使用

从内存使用上看,TrueAsync 同样具备优势。
在阻塞式模型中,每个 worker 通常都是一个独立进程或线程,并且都拥有一份完整的 Laravel 运行环境,包括:
- 服务容器
- 配置
- 路由
- 中间件
- 数据库管理器
而在 TrueAsync 中,通常只需要一个或少量 worker。
多个协程共享同一套已经启动完成的框架环境。真正需要为每个请求单独复制的,主要是请求级别的数据,例如:
- request
- session
- auth
因此,内存差异就体现出来了:
- TrueAsync:308 MB
- FrankenPHP Octane:400 MB
更低的内存使用并不是核心收益,但它是协程模型带来的额外优势。
六、用理论验证测试结果
基准测试结果可以用并发效率理论进行验证。
根据测试测量:
T_cpu ≈ 5 ms
T_io ≈ 23 ms其中:
T_cpu表示 PHP 执行时间T_io表示 SQL 等待时间
阻塞系数为:
T_io / T_cpu = 23 / 5 = 4.6Goetz 公式来自 Brian Goetz 的《Java Concurrency in Practice》,用于估算最优并发任务数:
N_opt = N_cores × (1 + T_io / T_cpu)对于拥有 16 个 worker 的阻塞式服务器来说,每个 worker 同一时间只能处理 1 个请求,因此理论吞吐量为:
Throughput = 16 / (T_cpu + T_io)
= 16 / 0.028
≈ 571 req/sOctane 的实测结果为:
601 req/s这个结果已经非常接近阻塞式模型的理论上限。
再看 TrueAsync。
对于使用 4 个 worker,并且最多约 200 个并发协程的 TrueAsync 来说:
N_opt = 16 × (1 + 4.6)
= 89.6 coroutines200 个协程已经足以覆盖最优并发规模。
根据 Little 定律:
λ = L / W可得:
λ = 200 / 0.028
≈ 7,142 req/s这是理论最大值。
实际测试结果约为 990 req/s,说明瓶颈并不在 CPU,而是在 PostgreSQL。
因此,理论验证了测试结论:
在 I/O 密集型工作负载中,当阻塞系数达到 4.6 时,协程模型能够比阻塞模型更高效地利用 CPU。
七、基准测试结论
1. Swoole ZTS ≈ Swoole NTS
Swoole ZTS 采用线程模型,Swoole NTS 采用进程模型。
但在本次测试中,二者吞吐量几乎没有差异。
原因很简单:
模型依旧是阻塞式的。 任意时刻,每个 worker 只能处理一个请求。
由于线程本地存储的存在,ZTS 的内存消耗反而高出约 5% - 10%。
2. Octane FrankenPHP ≈ Octane Swoole
Octane FrankenPHP 和 Octane Swoole 都属于阻塞式服务器,因此会遇到相同的根本限制。
FrankenPHP 的优势主要体现在内存上:
它使用一个 Go 进程,而不是多个独立 PHP 进程,因此更节省内存。
但是在吞吐量方面,两者表现基本相同。
3. TrueAsync 的最低收益约为 30% - 40%
即使将阻塞式 worker 池扩展到 22 - 27 个,以匹配 TrueAsync 的吞吐量,TrueAsync 依然在延迟和内存消耗上占优。
问题在于:
既然 TrueAsync 用 4 个 worker 就能完成同样的工作,为什么还要消耗 22 个 worker?
这正是协程模型的核心价值。
八、核心结论
对于 I/O 密集型工作负载,TrueAsync 的优势非常明显。
而典型 Web 应用,恰恰就是 I/O 密集型场景。
本次测试可以总结为:
TrueAsync 可以用少 5 - 6 倍的 worker 承载相同请求量,将延迟从数千毫秒降低到几十毫秒,并将内存消耗接近减半。
更具体地说:
- 阻塞式服务器需要更多 worker 才能堆出吞吐量;
- TrueAsync 可以通过协程在 I/O 等待期间切换执行权;
- CPU 不再大量浪费在等待数据库响应上;
- 单个 worker 可以同时承载更多请求;
- 延迟显著下降;
- 内存占用也随之降低。
最终结论是:
TrueAsync 并不是让 PHP 或 SQL 本身变快了,而是让 Laravel 在 I/O 等待期间不再空转。它提升的是资源利用率,而这正是 Web 应用性能优化中最关键的一环。