压力测试
ORM 压测对比报告 · Node / Bun / PHP
各框架官方推荐 ORM 的 1U1G 压测,共 16 条引擎×栈组合:
- Hono → Drizzle(
drizzle-orm@0.45.4+mysql2)——Bun、Node - Fastify → Drizzle(
fastify@5+ 同款 Drizzle/mysql2)——Bun、Node(Fastify 无内置 ORM,配 Drizzle 与 Hono 对齐) - Midway v4 → TypeORM(
typeorm@0.3.31+mysql2)——仅 Node - Hyperf 3.2 → hyperf/database(Eloquent +
pdo_mysql,Swoole 6.2)——PHP 8.3 - ThinkPHP 8.1 → think-orm(
v4.0+pdo_mysql,Apache mod_php prefork + opcache)——PHP 8.3 - Laravel 11.57 → Eloquent/hyf/database(
Illuminate\Database+pdo_mysql,Apache mod_php prefork + opcache)——PHP 8.3 - Laravel 11.57 + Octane → Eloquent(同上代码,
laravel/octane首方常驻内存服务器 + Swoole 6.2,workers=16)——PHP 8.3(Octane 默认关协程,并发=worker 进程数) - Yii2 2.0.51 → ActiveRecord(
yiisoft/yii2+pdo_mysql,Apache mod_php prefork + opcache)——PHP 8.3 - Yii3(
yiisoft/app)→yiisoft/active-record(yiisoft/db-mysql+pdo_mysql,Apache mod_php prefork + opcache)——PHP 8.3 - Webman 2.1 → Eloquent(
webman/database+illuminate/database@11,Workerman 5.1 常驻多进程 + opcache)——PHP 8.3(非协程) - Webman 2.1 → Eloquent(同上,Workerman + Swoole 6.2 协程事件循环、连接池,单进程)——PHP 8.3(协程)
同一份 CRUD、同表 hono_bench.items、同后端 MySQL、同 1U1G。表中加粗=该列严格最优(并列不标)。
1. 环境与方法
| 项 | 配置 |
|---|---|
| 沙箱 | WSL2 Ubuntu + podman,--cpus=1 --memory=1g(cgroup v2 硬限) |
| 后端/库 | 主机 MySQL 8.0.36;隔离库 hono_bench、最小权限账号 bench,主键 1..2048 |
| 运行时 | Node 22 / Bun latest / PHP 8.3.31+Swoole 6.2(Hyperf)/ PHP 8.3.31+Apache mod_php(ThinkPHP、Laravel、Yii2、Yii3,opcache)/ PHP 8.3.31+Workerman(Webman:非协程 Select 循环、协程 Swoole 6.2.3 循环,opcache)/ PHP 8.3.31+Octane·Swoole(Laravel Octane 常驻内存) |
| 公平性 | TS 只编译一次、Bun/Node 跑同字节 JS;规格/依赖/后端一致。Hyperf WORKER_NUM=1;Apache prefork 系(ThinkPHP/Laravel/Yii2/Yii3)预热 16、上限 50;Webman 非协程 count=50、协程 count=1+Swoole;Laravel Octane 与 Apache 轮同一份代码/库,仅把 HTTP 服务器换成 octane:start --server=swoole,workers=16、task-workers=4、max-requests=0(Octane 默认 enable_coroutine=false,并发=worker 进程数;cgroup 不改 /proc,cpuCount() 返 18,故显式钉 worker 免失控);opcache validate_timestamps=0;连接池统一 16 |
| 工具/档位 | autocannon 7;低 = 1 连接×15s,高 = 50 连接×30s |
| 采样 | CPU=cpu.stat 增量÷时长;内存=memory.current(前=空闲、后=峰值) |
场景:health(不碰 DB)、get(主键查一行)、post(插入)。16 组合 × 3 场景 × 2 档 = 96 组采样全部 errors/timeouts/non2xx = 0。
2. 结果总表
2.1 较高负载(50 连接 · 30s)— rps(高优)与 p50/p99 ms(低优)
| 栈 / 引擎 | health rps | get rps | post rps | health p50/p99 | get p50/p99 | post p50/p99 |
|---|---|---|---|---|---|---|
| Hono + Drizzle / Bun | 103,055 | 14,660 | 3,364 | 0 / 1 | 2 / 8 | 13 / 30 |
| Hono + Drizzle / Node | 38,938 | 6,433 | 3,116 | 1 / 4 | 6 / 31 | 14 / 41 |
| Hono + 裸 mysql2 / Bun | 85,225 | 17,984 | 3,165 | 0 / 2 | 2 / 6 | 14 / 31 |
| Hono + 裸 mysql2 / Node | 39,388 | 10,478 | 2,624 | 1 / 2 | 3 / 24 | 17 / 46 |
| Fastify + Drizzle / Bun | 72,294 | 8,123 | 2,668 | 0 / 2 | 5 / 21 | 15 / 93 |
| Fastify + Drizzle / Node | 27,580 | 2,843 | 3,086 | 1 / 6 | 13 / 57 | 15 / 38 |
| Hyperf + hyperf/database / PHP·Swoole | 12,934 | 3,935 | 2,668 | 3 / 9 | 12 / 19 | 18 / 33 |
| Midway + TypeORM / Node | 5,201 | 2,577 | 1,267 | 7 / 39 | 15 / 58 | 32 / 111 |
| Midway + 裸 mysql2 / Node | 5,331 | 3,377 | 2,525 | 7 / 33 | 10 / 49 | 16 / 57 |
| ThinkPHP + think-orm / PHP·Apache | 1,089 | 332 | 377 | 125 / 502 | 198 / 1101 | 200 / 732 |
| Laravel + Eloquent / PHP·Apache | 406 | 199 | 201 | 196 / 798 | 209 / 793 | 209 / 801 |
| Laravel + Eloquent / PHP·Octane·Swoole(常驻16进程) | 512 | 340 | 308 | 97 / 297 | 105 / 306 | 181 / 351 |
| Yii2 + ActiveRecord / PHP·Apache | 1,616 | 459 | 275 | 192 / 490 | 198 / 595 | 199 / 699 |
| Yii3 + active-record / PHP·Apache | 275 | 103 | 186 | 196 / 991 | 409 / 2948 | 216 / 799 |
| Webman + Eloquent / PHP·Workerman(非协程 50 进程) | 49,470 | 2,109 | 2,862 | 0 / 2 | 2 / 111 | 9 / 93 |
| Webman + Eloquent / PHP·Swoole(协程 1 进程池16) | 25,245 | 6,710 | 2,907 | 1 / 4 | 7 / 12 | 17 / 29 |
2.2 低负载(1 连接 · 15s)— rps(高优)
| 栈 / 引擎 | health | get | post |
|---|---|---|---|
| Hono + Drizzle / Bun | 17,031 | 2,247 | 579 |
| Hono + Drizzle / Node | 13,278 | 1,887 | 506 |
| Hono + 裸 mysql2 / Bun | 10,134 | 2,053 | 436 |
| Hono + 裸 mysql2 / Node | 13,205 | 2,585 | 316 |
| Fastify + Drizzle / Bun | 12,288 | 1,628 | 295 |
| Fastify + Drizzle / Node | 9,963 | 656 | 292 |
| Hyperf + hyperf/database / PHP·Swoole | 7,163 | 1,000 | 445 |
| Midway + TypeORM / Node | 4,315 | 1,433 | 292 |
| Midway + 裸 mysql2 / Node | 4,754 | 1,576 | 428 |
| ThinkPHP + think-orm / PHP·Apache | 1,384 | 320 | 204 |
| Laravel + Eloquent / PHP·Apache | 466 | 176 | 134 |
| Laravel + Eloquent / PHP·Octane·Swoole | 980 | 385 | 176 |
| Yii2 + ActiveRecord / PHP·Apache | 2,415 | 527 | 266 |
| Yii3 + active-record / PHP·Apache | 349 | 152 | 114 |
| Webman + Eloquent / PHP·Workerman(非协程) | 17,558 | 1,801 | 565 |
| Webman + Eloquent / PHP·Swoole(协程) | 12,011 | 1,606 | 512 |
2.3 资源占用(1U1G)— 内存 MB(低优)· 高负载 CPU
| 栈 / 引擎 | 空闲 RSS | 高负载峰值 RSS | 高负载 CPU |
|---|---|---|---|
| Hono + Drizzle / Bun | 18 MB | 52 MB | 0.95 |
| Hono + Drizzle / Node | 34 MB | 56 MB | 0.95 |
| Hono + 裸 mysql2 / Bun | 14 MB | 45 MB | 0.97 |
| Hono + 裸 mysql2 / Node | 26 MB | 51 MB | 0.98 |
| Fastify + Drizzle / Bun | 32 MB | 81 MB | 0.94 |
| Fastify + Drizzle / Node | 33 MB | 56 MB | 0.94 |
| Hyperf + hyperf/database / PHP·Swoole | 23 MB | 28 MB | 0.95 |
| Midway + TypeORM / Node | 49 MB | 77 MB | 0.96 |
| Midway + 裸 mysql2 / Node | 38 MB | 66 MB | 0.98 |
| ThinkPHP + think-orm / PHP·Apache | 25 MB | 92 MB | 0.98 |
| Laravel + Eloquent / PHP·Apache | 38 MB | 131 MB | 0.98 |
| Laravel + Eloquent / PHP·Octane·Swoole(常驻16进程) | 372 MB | 448 MB | 0.95 |
| Yii2 + ActiveRecord / PHP·Apache | 24 MB | 104 MB | 0.98 |
| Yii3 + active-record / PHP·Apache | 31 MB | 120 MB | 0.95 |
| Webman + Eloquent / PHP·Workerman(非协程 50 进程) | 75 MB | 123 MB | 0.98 |
| Webman + Eloquent / PHP·Swoole(协程 1 进程) | 22 MB | 29 MB | 0.95 |
高负载 CPU 全部 ≈1.0(单核饱和,rps 即天花板)。内存空闲 RSS 普遍偏低,峰值随 process-per-request 的 worker 数攀升——Apache prefork 系(ThinkPHP/Laravel/Yii2/Yii3)峰值 92
131MB 最高;常驻单/少进程系最省(Hyperf 28、Webman 协程 29、Node/Bun 系 4581)。Laravel Octane 例外:虽常驻,但 Octane 每个 Swoole worker 各自 boot 一份完整 Laravel(非纯 COW 共享),16 worker 空闲即 372MB、峰值 448MB,是全场最高——用 worker 进程数换并发,代价是内存。
3. 关键结论
① ORM 开销(同引擎 mysql2 → 推荐 ORM,高负载 rps):
| 对照 | health | get | post |
|---|---|---|---|
| Hono/Node → Drizzle | −1% | −39% | +19%* |
| Hono/Bun → Drizzle | +21%* | −18% | +6%* |
| Midway/Node → TypeORM | −2% | −24% | −50% |
health 持平(ORM 只作用 SQL 编组/映射);读慢 18~39%;TypeORM 写 create()+save() 最重(−50%)。* 受 MySQL/池抖动,方向”持平或略降”。
② 引擎 Bun vs Node:接不接 ORM 结论不变——Bun 单核吞吐约 Node 的 2.3~2.6×、常驻内存约一半。
③ 框架层光谱(同为 Node+Drizzle,高负载 health):Hono 38,938 > Fastify 27,580 > Midway 5,201。极简路由层差在走 DB 后被 ORM+MySQL 掩盖(Fastify get/post ≈ Hono)。
④ Hyperf(Swoole 单 worker):读写全面优于 Midway+TypeORM(get 3,935 / post 2,668 vs 2,577 / 1,267);峰值内存 28MB 全场最低。
⑤ Webman:协程 vs 非协程(同代码/镜像,仅事件循环不同):
| 指标(高负载) | 非协程(Workerman 50 进程) | 协程(Swoole 1 进程·池16) |
|---|---|---|
| health rps | 49,470 | 25,245 |
| get rps | 2,109 | 6,710 |
| 空闲/峰值内存 | 75 / 123 MB | 22 / 29 MB |
CPU 型 health 非协程胜(多进程摊薄、无协程切换);I/O 型 get 协程胜(16 连接池重叠 MySQL 等待,p99 111→12ms、内存仅 1/3)。纯计算走非协程,DB 密集走协程。
⑥ PHP 传统全栈框架(Apache prefork:ThinkPHP / Laravel / Yii2 / Yii3):process-per-request + 每请求框架引导把单核打死(cores ≈1),加连接不涨=纯 CPU 天花板,读写全面落后于常驻协程/事件循环系。内部次序 Yii2(health 1,616 / get 459 / post 275)> ThinkPHP(1,089/332/377)> Laravel(406/199/201)> Yii3(275/103/186,全场 16 栈最低,因每请求从零重建 DI 容器+AR 读路径对象水合)。
⑦ 常驻内存的收益与边界(同代码 Apache → Octane·Swoole):Laravel 只把 HTTP 服务器换成 Octane 常驻,health 406→512、get 199→340、post 201→308(约 1.5~1.7×,省掉每请求框架引导),但远不及把应用改写为协程/连接池的 Hyperf(get 3,935)与 Webman 协程(get 6,710)。根因:Octane 默认 enable_coroutine=false,并发=worker 进程数,1 核上 16 worker 对 50 连接仍要排队(high p99 ~300ms);且每个 worker 各自 boot 完整 Laravel,内存 372/448MB 为全场最高。结论:Octane 是”老 Laravel 项目的低成本提速”,但要 PHP 侧 I/O 吞吐与低内存,仍需为协程重写(Hyperf/Webman 协程)。Yii3 无同级的首方常驻 runner,故本轮只有 Laravel 有 Octane 常驻数据。