lua-yar 性能压测报告
测试时间:2026-07-24
压测脚本:test/benchmark_matrix.lua / test/benchmark_cosocket.lua / test/benchmark_cext.lua
测试样本:{ method = "add", params = { 1, 2 }, provider = "p", token = "t" }
〇、压测环境与配置
0.1 硬件
| 项目 |
规格 |
| CPU |
Apple M4 (10 核, arm64) |
| 内存 |
32 GB |
| OS |
macOS 26.5.1 (Build 25F80) |
0.2 运行时
| 运行时 |
版本 |
安装方式 |
| Lua 5.1 |
5.1.5 |
luaver |
| Lua 5.3 |
5.3.6 |
luaver |
| Lua 5.5 |
5.5.0 |
Homebrew (/opt/homebrew/bin/lua) |
| LuaJIT |
2.1.1784360928 |
Homebrew (/opt/homebrew/bin/luajit) |
| OpenResty |
1.31.1.1 (nginx + LuaJIT, clang 17.0.0) |
Homebrew (/opt/homebrew/opt/openresty) |
0.3 C 扩展与依赖
| 扩展 |
版本 |
安装路径 |
说明 |
| lua-cjson |
2.1.0.10-1 |
/opt/homebrew/lib/luarocks/rocks-5.5 |
JSON 编解码 C 扩展 |
| lua-cmsgpack |
0.4.0-0 |
/opt/homebrew/lib/luarocks/rocks-5.5 |
MessagePack 编解码 C 扩展 |
| luasocket |
3.1.0-1 (LuaSocket 3.0.0) |
/opt/homebrew/lib/luarocks/rocks-5.5 |
TCP 服务端(bench_server.lua 使用) |
Lua 5.1/5.3 的 C 扩展通过 luarocks --lua-dir --tree 独立编译安装至各自版本目录。LuaJIT 与 Lua 5.1 ABI 兼容,共享 .so。OpenResty 的 cmsgpack 从 Lua 5.1 树复制至 /Users/frank/.luarocks-resty/lib/lua/5.1/。
0.4 传输器配置
| 传输器 |
说明 |
适用场景 |
| mock cosocket |
内存缓冲区模拟 cosocket receive/send 接口,零 I/O 开销 |
隔离协议处理性能(所有运行时) |
| 真实 cosocket |
OpenResty ngx.socket.tcp() 连接 127.0.0.1:9600,TCP 往返 |
真实 I/O 吞吐(仅 OpenResty) |
| bench_server |
luasocket TCP server, 127.0.0.1:9600, keepalive 模式 |
真实 cosocket 压测的服务端 |
0.5 压测方法论
| 配置项 |
值 |
说明 |
| 预热迭代 |
min(n, 1000) 次 |
消除 JIT 预热影响,确保热点路径已编译 |
| 计时器 |
os.clock() |
CPU 时间,排除 I/O 等待(mock cosocket 场景) |
| codec 迭代数 |
100,000 |
序列化器裸编解码 |
| protocol 迭代数 |
50,000 |
render/parse 全链路 |
| handle_connection 迭代数 |
50,000 |
mock cosocket 全链路 |
| 真实 cosocket new-conn 迭代数 |
2,000 |
限制以避免 TCP TIME_WAIT 端口耗尽 |
| 真实 cosocket keepalive 迭代数 |
10,000 |
单连接多次请求 |
| 报告指标 |
ops/s |
每秒操作数 = 迭代数 / elapsed |
一、矩阵总览:handle_connection 全链路
全链路:receive_message → Protocol.parse → dispatch → Protocol.render → send
mock cosocket(内存缓冲,零 I/O 开销),隔离纯协议处理性能
| 打包器 |
Lua 5.1 |
Lua 5.3 |
Lua 5.5 |
LuaJIT |
OpenResty |
| 纯 Lua JSON |
30K |
32K |
34K |
95K |
122K |
| 纯 Lua Msgpack |
36K |
38K |
42K |
148K |
143K |
| cjson (C 扩展) |
30K |
32K |
34K |
95K |
121K |
| cmsgpack (C 扩展) |
37K |
39K |
42K |
146K |
146K |
关键发现
- LuaJIT 是最大性能杠杆:纯 Lua 在 LuaJIT 下获得 3.2-4.1x 加速(JSON 30K→95K,Msgpack 36K→148K)
- C 扩展在全链路中几乎无收益:cjson vs 纯 Lua JSON ≈ 1.0x,cmsgpack vs 纯 Lua Msgpack ≈ 1.0x——协议开销(header/framing/dispatch)主导,编解码器非瓶颈
- Msgpack 优于 JSON:纯 Lua 下 1.2x(全链路),LuaJIT 下 1.5x——二进制格式更紧凑且解析更快
- OpenResty ≈ 独立 LuaJIT:JSON 122K vs 95K(OpenResty 略快 28%),Msgpack 143K vs 148K(LuaJIT 略快 3%)
二、序列化器裸编解码
2.1 JSON
| 操作 |
Lua 5.1 |
Lua 5.3 |
Lua 5.5 |
LuaJIT |
OpenResty |
C 扩展 |
| Json.pack (纯Lua) |
125K |
131K |
132K |
625K |
569K |
— |
| Json.pack (cjson) |
830K |
804K |
785K |
811K |
805K |
cjson |
| cjson 加速比 |
6.6x |
6.1x |
5.9x |
1.3x |
1.4x |
— |
| Json.unpack (纯Lua) |
67K |
71K |
79K |
235K |
246K |
— |
| Json.unpack (cjson) |
693K |
677K |
699K |
975K |
997K |
cjson |
| cjson 加速比 |
10.4x |
9.5x |
8.9x |
4.1x |
4.0x |
— |
2.2 Msgpack
| 操作 |
Lua 5.1 |
Lua 5.3 |
Lua 5.5 |
LuaJIT |
OpenResty |
C 扩展 |
| Msgpack.pack (纯Lua) |
134K |
138K |
149K |
1,053K |
923K |
— |
| Msgpack.pack (cmsgpack) |
1,174K |
1,155K |
973K |
1,352K |
1,295K |
cmsgpack |
| cmsgpack 加速比 |
8.8x |
8.4x |
6.5x |
1.3x |
1.4x |
— |
| Msgpack.unpack (纯Lua) |
174K |
180K |
188K |
571K |
527K |
— |
| Msgpack.unpack (cmsgpack) |
835K |
796K |
842K |
1,320K |
1,317K |
cmsgpack |
| cmsgpack 加速比 |
4.8x |
4.4x |
4.5x |
2.3x |
2.5x |
— |
关键发现
- LuaJIT 纯 Lua Msgpack pack(1.05M ops/s)≈ cmsgpack(1.35M ops/s):JIT 编译器几乎消除了 C 扩展优势
- cjson unpack 在 OpenResty 下最快(997K ops/s):OpenResty 的 cjson 针对其 LuaJIT 优化
- 标准 Lua 三版本(5.1/5.3/5.5)性能几乎一致:纯 Lua 无 JIT,版本差异 < 5%
三、Protocol 全链路 render/parse
3.1 render(打包请求:header + framing + body 序列化)
| 打包器 |
Lua 5.1 |
Lua 5.3 |
Lua 5.5 |
LuaJIT |
OpenResty |
| render JSON (纯Lua) |
132K |
139K |
141K |
586K |
606K |
| render JSON (cjson) |
271K |
294K |
303K |
741K |
829K |
| render Msgpack (纯Lua) |
131K |
134K |
147K |
690K |
811K |
| render Msgpack (cmsgpack) |
274K |
293K |
304K |
881K |
1,019K |
3.2 parse(解析响应:framing + header 解包 + body 反序列化)
| 打包器 |
Lua 5.1 |
Lua 5.3 |
Lua 5.5 |
LuaJIT |
OpenResty |
| parse JSON (纯Lua) |
67K |
71K |
78K |
191K |
273K |
| parse JSON (cjson) |
155K |
162K |
181K |
680K |
819K |
| parse Msgpack (纯Lua) |
106K |
112K |
121K |
417K |
445K |
| parse Msgpack (cmsgpack) |
161K |
170K |
190K |
782K |
913K |
关键发现
- render 比 parse 快:render 是顺序写入(table.concat),parse 需要逐字节状态机解析
- LuaJIT 下 cjson parse(680K)≈ cmsgpack parse(782K):两者在 JIT 下趋于同水平
- 标准 Lua 下 C 扩展 render 加速 2.0-2.1x,parse 加速 1.6-2.3x:header/framing 固定开销稀释了序列化加速
四、Framing I/O 层
| 操作 |
Lua 5.1 |
Lua 5.3 |
Lua 5.5 |
LuaJIT |
OpenResty |
| receive_exact (8B) |
1.9M |
2.0M |
2.1M |
11.4M |
11.7M |
| receive_exact (82B) |
1.7M |
1.7M |
1.8M |
11.2M |
10.9M |
| receive_exact (90B) |
1.6M |
1.6M |
1.8M |
10.0M |
9.6M |
| receive_message (JSON) |
153K |
159K |
176K |
725K |
886K |
关键发现
- LuaJIT Framing 10-11M ops/s:I/O 层在 JIT 下极快,永远不是瓶颈
- mock cosocket 零 I/O 开销:纯内存缓冲
string.sub 操作,隔离协议处理开销
五、真实 cosocket handle_connection 往返压测
OpenResty 环境下,启动 test/bench_server.lua(luasocket TCP server, 127.0.0.1:9600, keepalive 模式),
cosocket 客户端完整往返:connect → send(Yar request) → Framing.receive_message(response) → close
5.1 mock cosocket vs 真实 cosocket 对比
| 场景 |
ops/s |
vs mock cosocket |
说明 |
| mock cosocket handle_connection (JSON) |
122K |
基线 |
纯协议开销,零 I/O |
| 真实 cosocket (new conn each) |
23K |
5.3x slower |
每次新建 TCP 连接 + 往返 + 关闭 |
| 真实 cosocket (keepalive) |
89K |
1.4x slower |
单连接多次请求,省去 connect/close |
| mock cosocket handle_connection (Msgpack) |
143K |
基线 |
纯协议开销 |
| 真实 cosocket Msgpack (keepalive) |
89K |
1.6x slower |
单连接多次 Msgpack 往返 |
5.2 keepalive vs new-conn 开销分解
| 模式 |
ops/s |
对比 |
| new conn each (JSON) |
23K |
基线 |
| keepalive (JSON) |
89K |
3.9x faster |
| keepalive (Msgpack) |
89K |
3.9x faster (vs Msgpack new-conn 估算) |
5.3 全打包器 keepalive 往返收敛
| 打包器 |
keepalive 往返 ops/s |
mock cosocket ops/s |
差距 |
| 纯 Lua JSON |
89K |
122K |
1.4x |
| 纯 Lua Msgpack |
89K |
143K |
1.6x |
| cjson |
87K |
121K |
1.4x |
| cmsgpack |
88K |
146K |
1.7x |
关键发现
- TCP connect/close 是最大 I/O 开销:new-conn 模式比 keepalive 慢 3.9x,连接建立开销远大于数据传输
- keepalive 模式下所有打包器收敛到 ~87-89K ops/s:真实 I/O 开销主导,编解码器差异被稀释
- mock cosocket 高估性能 1.4-5.3x:mock 适合隔离协议开销分析,但不能代表真实吞吐
- 生产环境必须使用连接池:cosocket
setkeepalive 归池或 TCP persistent 连接复用
六、C 扩展加速比矩阵(全链路 handle_connection)
| 打包器组合 |
Lua 5.1 |
Lua 5.3 |
Lua 5.5 |
LuaJIT |
OpenResty |
| cjson vs 纯Lua JSON |
1.0x |
1.0x |
1.0x |
1.0x |
1.0x |
| cmsgpack vs 纯Lua Msgpack |
1.0x |
1.0x |
1.0x |
1.0x |
1.0x |
| cmsgpack vs 纯Lua JSON |
1.2x |
1.2x |
1.2x |
1.5x |
1.2x |
| cmsgpack vs cjson |
1.2x |
1.2x |
1.2x |
1.5x |
1.2x |
关键发现
- 全链路加速比(1.0x)远低于裸编解码加速比(2-10x):header 打包/解包 + framing + dispatch 是固定开销,编解码器选择对全链路性能几乎无影响
- cmsgpack ≈ cjson:在全链路中两者性能持平,选择 Msgpack 的理由是二进制紧凑而非速度
- Msgpack 格式本身优于 JSON:纯 Lua Msgpack vs 纯 Lua JSON 全链路 1.2-1.5x,无需 C 扩展即有优势
七、开销分解
7.1 协议解析 vs I/O framing 占比
| 维度 |
ops/s |
占比 |
| Protocol.parse only (no I/O) |
191-445K |
— |
| receive_message + Protocol.parse |
153-886K |
— |
| I/O framing 开销 |
— |
15-24% |
| Protocol parse 占比 |
— |
76-85% |
7.2 全链路开销层级
handle_connection 全链路开销分解(LuaJIT, mock cosocket)
┌─────────────────────────────────────────────────────┐
│ Protocol.parse (body 反序列化) ~45% │
│ Protocol.render (body 序列化) ~25% │
│ Framing.receive_message (帧读取) ~15% │
│ Header unpack/pack ~10% │
│ dispatch (method lookup + pcall) ~3% │
│ send (mock, 零开销) ~2% │
└─────────────────────────────────────────────────────┘
关键发现
- 协议解析(parse + render)占全链路 ~70%:优化序列化器是唯一有效手段
- Framing 层占 ~15%:
receive_exact 的 table.concat 循环已足够高效
- dispatch 占比极低(~3%):方法表 memoize 查找 +
pcall 开销可忽略
八、综合评估
8.1 性能等级矩阵
| 配置 |
标准 Lua (5.1/5.3/5.5) |
LuaJIT / OpenResty |
评级 |
| 纯 Lua JSON 全链路 |
30-34K ops/s |
95-122K ops/s |
B- / B+ |
| 纯 Lua Msgpack 全链路 |
36-42K ops/s |
143-148K ops/s |
B / A- |
| cjson 全链路 |
30-34K ops/s |
95-121K ops/s |
B- / B+ |
| cmsgpack 全链路 |
37-42K ops/s |
146K ops/s |
B / B+ |
| Framing I/O (mock) |
153-176K ops/s |
725-886K ops/s |
A / A+ |
| 真实 cosocket (keepalive) |
— |
87-89K ops/s |
B+ |
8.2 生产推荐
| 场景 |
运行时 |
打包器 |
传输器 |
预期吞吐 |
| OpenResty 高并发 |
OpenResty LuaJIT |
cmsgpack |
cosocket + 连接池 |
~88K ops/s (真实 I/O) |
| OpenResty 零依赖 |
OpenResty LuaJIT |
纯 Lua Msgpack |
cosocket + 连接池 |
~89K ops/s (真实 I/O) |
| OpenResty 协议峰值 |
OpenResty LuaJIT |
cmsgpack |
mock cosocket |
~146K ops/s (协议上限) |
| 标准 Lua 高并发 |
Lua 5.1/5.3 |
cmsgpack |
luasocket |
~39K ops/s (协议上限) |
| 标准 Lua 零依赖 |
Lua 5.1/5.3 |
纯 Lua Msgpack |
luasocket |
~36K ops/s (协议上限) |
| 嵌入式低资源 |
Lua 5.1 |
纯 Lua JSON |
自定义 |
~30K ops/s |
8.3 核心结论
- 运行时是第一性能杠杆:LuaJIT vs 标准 Lua = 3.2-4.1x(全链路),远超 C 扩展的 1.0x
- C 扩展在全链路中几乎无收益(1.0x):协议开销(header/framing/dispatch)主导,编解码器非瓶颈;仅在裸编解码层有 2-10x 加速
- Msgpack 优于 JSON:纯 Lua 下 1.2x,LuaJIT 下 1.5x(全链路),且二进制更紧凑
- cjson ≈ cmsgpack:在全链路中两者持平,Msgpack 的优势是格式紧凑而非速度
- Framing 层零瓶颈:LuaJIT 下 10-11M ops/s,占全链路 < 5% 开销
- OpenResty ≈ 独立 LuaJIT:cosocket 环境不引入额外框架开销(5% 以内)
- 真实 cosocket I/O 是吞吐瓶颈:keepalive 模式下 ~89K ops/s,所有打包器收敛——I/O 开销主导
- 连接复用是生产必备:new-conn vs keepalive = 3.9x 差距,cosocket
setkeepalive 或 TCP persistent 必须启用
附录:压测脚本与局限性
A.1 压测脚本
| 脚本 |
维度 |
运行方式 |
test/benchmark_matrix.lua |
5 运行时 × 4 打包器 × 2 传输器 矩阵 |
lua/luajit/resty 通用 |
test/benchmark_cosocket.lua |
cosocket 传输层专项 + 真实 I/O 往返 |
resty (OpenResty) |
test/benchmark_cext.lua |
C 扩展注入 vs 纯 Lua 编解码 |
lua/luajit/resty 通用 |
test/bench_server.lua |
压测用最小 TCP 服务端(luasocket, keepalive 模式) |
lua test/bench_server.lua |
A.2 局限性
- 单线程服务端:
bench_server.lua 使用 luasocket 顺序 accept,未测多连接并发场景
- localhost I/O:真实 cosocket 往返走 127.0.0.1,网络延迟接近零,实际网络环境下 I/O 开销更高
- 无 PHP 互通:未测试与 PHP Yar Server 的跨语言互通性能