测试时间:2026-07-24
压测脚本:test/benchmark_matrix.lua / test/benchmark_cosocket.lua / test/benchmark_cext.lua
测试样本:{ method = "add", params = { 1, 2 }, provider = "p", token = "t" }
〇、压测环境与配置
| 项目 | 规格 |
|---|
| CPU | Apple M4 (10 核, arm64) |
| 内存 | 32 GB |
| OS | macOS 26.5.1 (Build 25F80) |
| 运行时 | 版本 | 安装方式 |
|---|
| 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) |
| 扩展 | 版本 | 安装路径 | 说明 |
|---|
| 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/。
| 传输器 | 说明 | 适用场景 |
|---|
| 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 压测的服务端 |
| 配置项 | 值 | 说明 |
|---|
| 预热迭代 | 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 |
全链路: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%)
二、序列化器裸编解码
| 操作 | 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 | — |
| 操作 | 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%
| 打包器 | 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 |
| 打包器 | 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 固定开销稀释了序列化加速
| 操作 | 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 操作,隔离协议处理开销
OpenResty 环境下,启动 test/bench_server.lua(luasocket TCP server, 127.0.0.1:9600, keepalive 模式),
cosocket 客户端完整往返:connect → send(Yar request) → Framing.receive_message(response) → close
| 场景 | 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 往返 |
| 模式 | ops/s | 对比 |
|---|
| new conn each (JSON) | 23K | 基线 |
| keepalive (JSON) | 89K | 3.9x faster |
| keepalive (Msgpack) | 89K | 3.9x faster (vs Msgpack new-conn 估算) |
| 打包器 | 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 连接复用
| 打包器组合 | 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 扩展即有优势
七、开销分解
| 维度 | 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% |
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 开销可忽略
八、综合评估
| 配置 | 标准 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+ |
| 场景 | 运行时 | 打包器 | 传输器 | 预期吞吐 |
|---|
| 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 |
- 运行时是第一性能杠杆: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 必须启用
附录:压测脚本与局限性
| 脚本 | 维度 | 运行方式 |
|---|
| `test/benchmark_matrix.lua$ | 5 运行时 \times 4 打包器 \times 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 |
- 单线程服务端:
bench_server.lua 使用 luasocket 顺序 accept,未测多连接并发场景
- localhost I/O:真实 cosocket 往返走 127.0.0.1,网络延迟接近零,实际网络环境下 I/O 开销更高
- 无 PHP 互通:未测试与 PHP Yar Server 的跨语言互通性能