协程框架高并发翻车了?三个C++ Web框架实测,结果出乎意料
上一版测完总觉得哪里不对,于是改了配置又跑了一遍——这次结果老实多了。
前言:为什么重新测了一遍
这篇文章其实是第二版了。上一篇发出来之后我越想越不对劲——当时每个容器只给了 512MB 内存,fd 上限也没显式调高,跑 10K 并发的时候实际上根本没撑到一万个连接。wrk 报了一堆 connect 8983 错误,真正建立的有效连接也就一千出头,所谓的"高并发 10K"测试本质上测的还是千级并发。那组数据里 Hical 高并发"遥遥领先"的结论,现在看有水分。
这次我把配置拉满了:每容器 1024MB 内存、nofile 上调到 65536,VM 内核参数也做了对应调优(somaxconn、tcp_max_syn_backlog、端口范围等),确保 wrk 发起的一万个连接能真正建立起来。同时精简了文章的对比范围——压测还是 6 个框架一起跑的(数据完整保留在文章末尾),但本文只聚焦 Hical、Drogon、Cinatra 三个第一梯队选手做深入分析。这三个在上一版常规场景里已经拉开了和其余框架的差距,继续带着 91 QPS 的 cpp-httplib 同框只会让图表失真。
结果确实和上次不一样,尤其是高并发部分的排名翻了个个儿。先把结论放在前面:常规负载下三个框架打得很近,但各自有各自的强项——Hical 在 JSON 和中间件场景领先,Cinatra 在纯吞吐和 JSON Echo 上更快,Drogon 在高并发场景下表现最好。
测试环境
宿主机硬件:
| 项目 | 配置 |
|---|---|
| 处理器 | Intel Core i7-11700K @ 3.60GHz(8核16线程) |
| 内存 | 32 GB |
| 存储 | SSD(900 GB) |
| 宿主系统 | Windows 10 Enterprise LTSC 2021 |
虚拟化环境:
| 项目 | 配置 |
|---|---|
| 虚拟化平台 | Oracle VM VirtualBox 7.1(半虚拟化接口: KVM) |
| Guest OS | Ubuntu 24.04.3 LTS Server |
| VM 分配 | 16 GB RAM / 8 CPU |
| Docker Engine | 29.4.3 + Compose v5.1.3 |
| 每框架容器 | 4 CPU + 1GB RAM,nofile=65536 |
| 压测工具 | wrk 4.1.0(独立容器),4 线程 |
| 默认并发 | 100 连接 |
| 持续时间 | 30 秒 |
| 编译器 | GCC 14.2(Ubuntu 24.04) |
| 优化级别 | Release(-O2) |
| 采样方式 | 每场景连续跑 3 轮,取算术平均值 |
注:所有框架运行在同一台 VM 的 Docker 容器中,wrk 也在同一 Docker 网络内发起请求(容器间通信,无宿主机网络栈介入)。VirtualBox 虚拟化会引入一定开销,绝对 QPS 数值会低于裸机,但各框架的相对排名通常稳定。
框架版本:
| 框架 | 版本 | 构建方式 | I/O 模型 | JSON 库 |
|---|---|---|---|---|
| Hical | v2.6.3 | 复制源码编译 | C++20 协程 (Boost.Asio) | Boost.JSON |
| Drogon | v1.9.8 | 源码编译 | 回调 (Trantor) | JsonCpp |
| Cinatra | latest | FetchContent | C++20 协程 (自有实现) | iguana |
基础场景:Hello World / JSON
先看最简单的纯文本和 JSON 响应,这基本测的是框架自身的"底层开销"——HTTP 解析、路由匹配、响应序列化这些。
| 场景 | Hical | Drogon | Cinatra |
|---|---|---|---|
| Hello World (GET /) | 153,871 | 140,536 | 165,717 |
| JSON 响应 (GET /api/status) | 141,499 | 95,090 | 128,391 |
| JSON Echo (POST /api/echo) | 123,585 | 69,343 | 131,440 |
| 路径参数 (GET /users/42) | 140,285 | 86,487 | 125,262 |
单位:Requests/sec(三轮平均值)
几个观察:
-
Hello World:三者都在 14w~17w 的水平,Cinatra 166k 略高,Hical 154k,Drogon 141k。Cinatra 和 Hical 差距 8%,属于同一档;Drogon 稍低一些。
-
JSON 序列化(GET /api/status):Hical 141k 领先,Cinatra 128k 紧随,Drogon 95k 落后较多。这里有个重要背景:Drogon 用的是 JsonCpp 库,而 Hical 用 Boost.JSON,Cinatra 用 iguana。JSON 库本身的性能差异直接体现在这里。
-
JSON Echo(POST 反序列化+序列化):Cinatra 131k 略快于 Hical 124k,iguana 的反序列化确实快。Drogon 只有 69k,差距比较明显,大概率是 JsonCpp 的开销。
-
路径参数:Hical 140k 最高,Cinatra 125k 次之,Drogon 86k 偏低。
小结:不涉及 JSON 的话 Cinatra 最快,一旦碰 JSON 就是 Hical 的地盘。Drogon 吃了 JsonCpp 的亏,不过 Hello World 纯吞吐也就比前两个低 10% 左右,不至于掉队。
中间件场景:真正的分水岭
中间件测试是这次最有意思的部分。为了公平,测试分了两种模式:
- 原生中间件:Hical 用 RouteGroup 洋葱链,Drogon 用 HttpFilter,Cinatra 因为没有运行时中间件机制,用
std::function调用链模拟等价开销。 - Hical SyncMW:Hical 独有的同步中间件快速路径(全 Sync 时零协程帧,纯栈调用),其他框架继续用各自的原生机制。
| 场景 | Hical | Drogon | Cinatra |
|---|---|---|---|
| 0 层中间件 | 140,837 | 106,789 | 132,121 |
| 3 层(原生) | 136,302 | 105,171 | 131,595 |
| 10 层(原生) | 93,402 | 104,942 | 137,830 |
| 3 层(Hical SyncMW) | 141,160 | 94,588 | 129,701 |
| 10 层(Hical SyncMW) | 139,685 | 100,652 | 127,711 |
单位:Requests/sec(三轮平均值)
这里有个很值得讨论的现象:
Hical 的原生异步中间件在 10 层时性能下降明显(从 0 层的 141k 掉到 93k,降幅 34%),而 Drogon 和 Cinatra 几乎不受影响。为什么?因为 Hical 的洋葱模型中间件每层都是一个 co_await,10 层就是 10 次协程挂起/恢复 + 10 个协程帧的堆分配。Drogon 的 HttpFilter 是回调链(不走协程),Cinatra 的测试代码是普通函数调用链——人家根本没有协程帧开销。
但这也是为什么 Hical 提供了 SyncMW 快速路径的原因。当所有中间件都使用 SyncBeforeHandler 且 handler 本身也是同步签名时,RouteGroup 会直接注册为 SyncRouteHandler,走 dispatchSync() 路径——零协程帧,整条链路从中间件到 handler 都是纯栈上函数调用,和 Drogon/Cinatra 的回调/函数调用模型站在同一起跑线上。QPS 从 93k 恢复到 140k,完全消除了协程帧开销,和 0 层中间件的 141k 持平——因为 SyncMW 路径连路由分发本身的协程帧都省了。
公平性说明:Cinatra 在中间件测试中使用的是 std::function 调用链模拟,而不是框架原生的中间件机制(Cinatra 的 middleware 是编译时模板,不支持运行时动态添加)。这意味着 Cinatra 的中间件数字测的是"函数调用链的开销",而非框架中间件调度本身的开销。这是测试设计上的限制——不同框架的中间件机制差异太大,很难做到 100% 等价对比。
高并发:1000 和 10000 连接
这是最能体现框架"韧性"的测试。
| 并发连接数 | Hical | Drogon | Cinatra |
|---|---|---|---|
| 100 | 137,522 | 145,663 | 159,519 |
| 1,000 | 63,077 | 81,646 | 69,797 |
| 10,000 | 59,451 | 100,534 | 63,971 |
单位:Requests/sec(三轮平均值)
注:10,000 并发时 Cinatra 三轮均出现 timeout(808~2719 个),Hical 第一轮有 1112 个 timeout 后两轮无错误,Drogon 第一轮有 4022 个 timeout 但后两轮几乎为零。
这组数据很有意思:
- 100 并发:Cinatra 160k 略高,Drogon 146k,Hical 138k。三者差距在 15% 以内。
- 1000 并发:所有框架都有较大衰减。Drogon 82k 表现最好,Cinatra 70k 次之,Hical 63k。
- 10000 并发:Drogon 101k 一骑绝尘,是 Hical(59k)和 Cinatra(64k)的 1.6~1.7 倍。
这组结果我没预料到。跑之前我以为 Hical 的 SO_REUSEPORT + 多 io_context 架构在高并发下会有优势,结果被 Drogon 的回调模型按在地上。两个 C++20 协程框架(Hical 和 Cinatra)在 10K 连接下都掉到了 60k 附近,而 Drogon 稳在 101k。协程模型下每个连接维护一个协程帧,一万个帧的管理和调度本身就是开销,Drogon 的回调模型不存在这个问题。
另外要提一嘴:Cinatra 在 10K 时三轮都有数百到数千个 timeout,这也会拖低 QPS。Hical 除了第一轮外零错误但 QPS 同样低,说明瓶颈不在连接管理而在协程调度本身。
延迟对比
除了 QPS,延迟也很重要:
| 场景 | Hical Avg | Drogon Avg | Cinatra Avg |
|---|---|---|---|
| Hello World (c=100) | 1.91 ms | 1.71 ms | 1.77 ms |
| 高并发 1000 | 14.94 ms | 12.28 ms | 15.37 ms |
| 高并发 10000 | 157.22 ms | 65.86 ms | 89.80 ms |
数据来自 wrk 的 Avg Latency 输出,三轮平均值。
c=100 时三者都在 1.7~1.9ms,没啥好说的。往上拉就有意思了:
- c=1000:Drogon 12ms,Hical 15ms,Cinatra 15ms——还在一个量级
- c=10000:Drogon 66ms,Cinatra 90ms,Hical 157ms——Hical 直接拉胯,是 Drogon 的 2.4 倍
和 QPS 数据对得上,高并发就是 Hical 当前最大的短板,后面得好好优化。
资源占用
| 指标 | Hical | Drogon | Cinatra |
|---|---|---|---|
| 空载内存 | 200.4 MB | 93.5 MB | 56.6 MB |
| 满载内存 | 197.0 MB | 93.5 MB | 55.8 MB |
| 二进制大小 | 2.1 MB | 1.9 MB | 528 KB |
| 代码行数(bench) | 149 行 | 234 行 | 182 行 |
Hical 空载就吃了 200 MB,这是 PMR 内存池预分配的代价。好处是运行时几乎不再向系统要内存,坏处是启动就占这么大一坨。Drogon 94 MB,Cinatra 57 MB 最省。
有意思的是三者满载和空载内存几乎没变化——100 并发的压力下连接本身基本不额外吃内存。
综合评价
| 维度 | Hical | Drogon | Cinatra |
|---|---|---|---|
| 常规吞吐 (c=100) | 优秀 | 良好 | 优秀 |
| JSON 处理 | 快 (Boost.JSON) | 慢 (JsonCpp) | 快 (iguana) |
| 中间件 (少量/SyncMW) | 最快 | 良好 | 优秀 |
| 中间件 (大量,异步) | 有衰减 | 稳定 | 稳定 |
| 高并发 (c=1000+) | 一般 | 最好 | 良好 |
| 高并发延迟 | 偏高 | 最低 | 中等 |
| 内存占用 | 高 (200MB) | 中 (94MB) | 低 (57MB) |
| 生态成熟度 | 新框架 | 成熟 | 中等 |
几点坦诚的说明
利益相关:我是 Hical 的作者,这个测试从设计到执行都是我一个人干的。Docker 隔离、相同资源限制、wrk 统一配置——这些我尽力做到了,但屁股决定脑袋,测试设计上的视角偏差肯定有。不服的话欢迎自己跑一遍打我脸。
中间件测试其实不太公平:三个框架的中间件机制完全不一样——Hical 是运行时洋葱链、Drogon 是 HttpFilter 链、Cinatra 压根没有运行时中间件。我尽量模拟等价语义了,但严格说测的不是同一个东西。
关于数据波动:每个场景跑了 3 轮取平均。wrk 在 Docker 里的波动大概 5%~10%,三轮能抹平大部分偶然因素,但不可能完全干净。原始数据附在文末,你可以自己看各轮之间的差异有多大。
双层虚拟化的影响:VirtualBox VM 里跑 Docker,绝对 QPS 肯定比裸机低。但大家都在同一个环境里跑,相对排名应该是稳定的。裸机 Linux 上数字会更好看,不过排名大概率不会变。
选框架不能只看 QPS:Drogon 有完整的 ORM、WebSocket、HTTP 客户端;Cinatra 轻巧好上手;Hical 在中间件和 JSON 这块有些自己的想法。性能测试只是选型的一个参考维度,生态、文档、社区活跃度这些才是长期用下去绕不开的。
复现方法
所有测试代码、Docker 配置和压测脚本均已开源在仓库的 benchmark/ 目录下,详见仓库 benchmark/README.md,包含完整的构建、启动、压测、采集和清理流程。
git clone https://github.com/hical61/hical.git
cd hical/benchmark
# 后续步骤参照 benchmark/README.md
注:只需三步就能在你自己的机器上跑出结果。如果和我的数据有明显差异,欢迎开 issue 讨论。
TL;DR
- 三个框架在 100 并发下性能相近,都在 14w~17w QPS
- JSON 处理:Hical (Boost.JSON) 和 Cinatra (iguana) 明显快于 Drogon (JsonCpp)
- 中间件:Hical SyncMW 零协程帧路径最快,但原生异步中间件层数增多时有衰减
- 高并发 (1000+):Drogon 的回调模型表现最稳健,协程框架都有不同程度下降
- 内存占用:Cinatra 最低 (57MB),Hical 最高 (200MB,PMR 预分配)
- 选框架不能只看 QPS,生态和维护同样重要
附录:三轮原始数据
以下是三轮测试的原始 QPS 数据,供交叉验证。每轮为独立的 30 秒 wrk 测试,三轮连续执行,未重启容器。
第一轮(2026-05-25 10:08)
| 场景 | Hical | Drogon | Cinatra |
|---|---|---|---|
| Hello World (c=100) | 156,796.12 | 127,980.96 | 163,774.61 |
| JSON 响应 (c=100) | 137,491.36 | 74,466.33 | 127,707.87 |
| JSON Echo (c=100) | 111,098.91 | 62,833.71 | 135,691.72 |
| 路径参数 (c=100) | 150,121.59 | 88,839.01 | 118,966.68 |
| 中间件 0 层 (c=100) | 153,207.23 | 112,848.48 | 136,976.94 |
| 中间件 3 层原生 (c=100) | 136,898.55 | 110,966.38 | 138,247.89 |
| 中间件 10 层原生 (c=100) | 90,629.82 | 90,728.35 | 144,136.85 |
| SyncMW 3 层 (c=100) | 136,901.79 | 101,765.38 | 124,463.02 |
| SyncMW 10 层 (c=100) | 141,379.19 | 99,782.52 | 125,834.13 |
| 高并发 (c=100) | 110,907.90 | 125,923.51 | 166,994.13 |
| 高并发 (c=1000) | 62,416.43 | 82,525.51 | 73,922.75 |
| 高并发 (c=10000) | 59,184.07 | 98,286.46 | 70,999.25 |
高并发 10K Socket Errors: Hical timeout=1112, Drogon timeout=4022, Cinatra timeout=2719
第二轮(2026-05-25 10:55)
| 场景 | Hical | Drogon | Cinatra |
|---|---|---|---|
| Hello World (c=100) | 165,965.45 | 168,045.72 | 165,510.87 |
| JSON 响应 (c=100) | 145,883.87 | 100,499.66 | 135,568.09 |
| JSON Echo (c=100) | 131,825.18 | 81,832.00 | 141,118.30 |
| 路径参数 (c=100) | 130,455.35 | 77,424.95 | 126,874.50 |
| 中间件 0 层 (c=100) | 138,603.05 | 99,891.09 | 121,208.64 |
| 中间件 3 层原生 (c=100) | 131,093.74 | 90,841.49 | 126,053.21 |
| 中间件 10 层原生 (c=100) | 93,569.44 | 114,919.03 | 141,083.12 |
| SyncMW 3 层 (c=100) | 149,265.85 | 80,593.01 | 127,425.23 |
| SyncMW 10 层 (c=100) | 135,342.89 | 91,646.87 | 133,061.49 |
| 高并发 (c=100) | 140,643.56 | 149,802.87 | 149,170.97 |
| 高并发 (c=1000) | 65,478.39 | 86,809.88 | 71,551.21 |
| 高并发 (c=10000) | 59,258.94 | 94,281.89 | 62,137.10 |
高并发 10K Socket Errors: Hical 无错误, Drogon 无错误, Cinatra timeout=919
第三轮(2026-05-25 11:42)
| 场景 | Hical | Drogon | Cinatra |
|---|---|---|---|
| Hello World (c=100) | 138,851.55 | 125,580.99 | 167,866.51 |
| JSON 响应 (c=100) | 141,122.19 | 110,304.60 | 121,898.13 |
| JSON Echo (c=100) | 127,830.71 | 63,364.29 | 117,510.76 |
| 路径参数 (c=100) | 140,279.31 | 93,197.92 | 129,945.28 |
| 中间件 0 层 (c=100) | 130,700.88 | 107,628.47 | 138,178.82 |
| 中间件 3 层原生 (c=100) | 140,912.53 | 113,704.01 | 130,484.50 |
| 中间件 10 层原生 (c=100) | 96,007.00 | 109,177.63 | 128,268.96 |
| SyncMW 3 层 (c=100) | 137,311.12 | 101,405.52 | 137,215.18 |
| SyncMW 10 层 (c=100) | 142,333.72 | 110,527.95 | 124,236.71 |
| 高并发 (c=100) | 161,013.19 | 161,261.49 | 162,390.92 |
| 高并发 (c=1000) | 61,336.68 | 75,602.27 | 63,918.27 |
| 高并发 (c=10000) | 59,910.86 | 109,031.90 | 58,776.93 |
高并发 10K Socket Errors: Hical 无错误, Drogon timeout=7, Cinatra timeout=1062
测试日期:2026-05-25 | 宿主机:i7-11700K / 32GB | VM:VirtualBox 7.1 + Ubuntu 24.04 (16GB/8CPU) | 容器:Docker 29.4,4 CPU / 1GB per container,nofile=65536 | 工具:wrk 4.1.0 | 每场景 3 轮取平均值
后续更新:v2.6.7 重测(2026-07-15)
一个半月过去,Hical 发了几个版本,到 v2.6.7 的时候高并发这块做了几项针对性优化。于是拿同一套环境、同样的 wrk 参数重新跑了三轮。不过有个重要变化得先交代——benchmark 代码这次也改了,不是 Hical 这边改的,是 Drogon 的中间件实现方式换掉了。所以结果跟上一版相比变化挺大,高并发尤其明显,但中间件场景的数据对比也需要重新理解。下面细说。
从 v2.6.3 到 v2.6.7 改了什么
从 v2.6.3 发到现在这版,中间经过了 v2.6.4、v2.6.5、v2.6.6 四个版本。每个版本都有 Performance 条目,加起来快 20 项了,全列出来太啰嗦,按版本梳理一下跟 HTTP 吞吐直接相关的:
v2.6.4(发 v2.6.3 之后五天就出了)主要干了几件事:IdleScanner 集中式空闲扫描替代了每个连接独立 steady_timer 协程——以前每个连接一个 timer 协程在那蹲着等超时,现在是每个 io_context 一个定时器扫一遍侵入式链表,省掉了海量协程帧和 epoll_ctl 调用。EventLoopPool 的负载分发从 round-robin 改成了最少连接(挑 connectionCount_ 最小的 loop),连接分布更均匀。GenericConnection 的 writeLoop drain 加了 kMaxDrainBatch = 256 上限,防止突发队列饿死 io_context 上的其他任务。还有个响应前缀模板化——连接级预构建了 Server/Connection/Date 三个通用头的 wire bytes(约 90B),keep-alive 请求直接 memcpy,省了三次 HeaderMap::insert + 序列化循环。
v2.6.5的重点是 ReadBufferPool——thread_local 无锁的 8KB 缓冲池,BufferHandle RAII 借还。空闲连接不再霸着 8KB 读缓冲,来了请求借一块、写完了还回去。加上 GenericConnection::inputBuffer_ 从普通 string 改成了 optional<PmrBuffer> 懒分配(readLoop 第一次进来才创建),空闲连接从 17.44 KB 降到了约 8 KB。另外还有 HeaderMap::toLower() 和 Router::urlDecode() 两个热路径都换成了编译期 256 查表,HTTP 解析时顺手把 Connection 头抓出来省了后面单独扫一遍。
v2.6.6 改了 keep-alive 的空闲等待方式——原来是 async_wait(wait_read),每次都要调 epoll_ctl(MOD) 注册可读事件;改成 256B 栈缓冲 async_read_some 投机读,利用 Asio 内部的投机路径,空闲连接完全不走 epoll_ctl。strace 验证过,10 秒内的 epoll_ctl 调用从 32,563 降到 9,173(−71.8%),CPU 时间占比从 26.51% 降到 5.57%。AsioEventLoop 的 concurrency_hint 也暴露出来可配了,默认设成 1,跟 Hical 一线程一 io_context 的模型匹配,epoll 走单线程优化路径少调几次 epoll_ctl。另外 setHeader 参数从 const std::string& 改成了 std::string_view,字符串字面量传参不再隐式构造临时 std::string,per-request 少一次堆分配。
v2.6.7(就是这次压测的版本)改了三个热路径:乐观同步写——小响应先拿 write_some 试一把同步写,写完直接 co_return,不挂协程不进 reactor 完成队列。只有 would_block(内核发送缓冲区满了)或者只写了一部分的情况才回退 async_write。有个坑踩了半天——Asio 内部有两套 non_blocking 标志位,sync_send 只检查 user_set_non_blocking,要不是 @hyird 在 issue #11 里指出来,我都不知道 handleSession 入口得显式 socket.non_blocking(true),否则 write_some 在 EAGAIN 时可能掉进 poll() 卡死 io 线程。第二个是 readBuf 提前归还——响应写完立刻 release() 还给 ReadBufferPool,别等到协程末尾析构,10K 连接在途的高并发场景下峰值缓冲从 80MB 砍到约 40MB。第三个是消除转发协程帧——中间件包装 lambda 从 co_return co_await mw(r, next) 改成 return mw(r, next)(类型推导一致),lambda 退化成普通函数,10 层中间件从每请求 20 帧降到 10 帧。剩下还有几个细碎的:HttpRequest attributes 换透明哈希(string_view 零分配查找)、preparePayload 幂等化、WebSocket 帧缓冲复用。总体效果是万连接吞吐 +58%、内存 −30%、中间件链 +7.6%。
这几个版本累计下来,一条请求从 accept 到响应发完的热路径上,能省的堆分配基本都省了、能不走协程挂起的就不挂、能不走 epoll_ctl 的就不走。
Benchmark 本身也修了一个口径问题
在开始看数据之前,得交代一件事——这次重测不只是 Hical 版本变了,benchmark 代码也有一个重要改动:Drogon 的中间件实现方式换了。
上一版(v2.6.3)压测时,Drogon 的 /middleware/3 和 /middleware/10 用的是 HttpFilter。HttpFilter 是什么?是在同一个 EventLoop 线程上做纯同步回调递归——nextCb() 就是普通函数调用,不建协程帧、不挂起不恢复。而 Hical 的异步中间件是 co_await next(req),每层建一个协程帧、每层挂起恢复一次。这两者根本不是一个机制——用回调链跟协程洋葱比"谁层数增加衰减更少",就像拿自行车和汽车比谁更省油,没什么意义。
所以这次把口径对齐了(issue #12):Drogon 的 /middleware/* 端点换成了 HttpCoroMiddleware——Drogon v1.9.8 原生支持的协程中间件,语法就是标准的 co_await,每层挂起+恢复,跟 Hical 的异步中间件语义严格对齐。同时 /sync-filter/* 两端点(原 /sync-middleware/*,名字也改了,因为"同步中间件"不够准确,这就是同步过滤链)继续用 HttpFilter,跟 Hical 的 SyncBeforeHandler 零协程帧快速路径对齐。
简单说:上一版中间件测试是"苹果比橘子",v2.6.7 修成了"苹果比苹果、橘子比橘子"——协程洋葱跟协程洋葱比,同步过滤跟同步过滤比。
这意味着 v2.6.7 里 Drogon 的中间件数据跟 v2.6.3 不可直接对比,因为测的就不是同一个东西了。其他五个框架的 benchmark 代码没变(只统一改了端点路径名)。
Hical 自身前后对比
同一套环境,Hical 版本从 v2.6.3 换到 v2.6.7。Crow/Oat++/cpp-httplib/Cinatra 的版本和 benchmark 代码都没变,Drogon 的版本没变但中间件 benchmark 代码有上述修改。
| 场景 | v2.6.3 | v2.6.7 | 变化 |
|---|---|---|---|
| Hello World (c=100) | 153,871 | 130,413 | −15.2% |
| JSON 响应 (c=100) | 141,499 | 141,285 | −0.2% |
| JSON Echo (c=100) | 123,585 | 132,890 | +7.5% |
| 路径参数 (c=100) | 140,285 | 140,555 | +0.2% |
| 中间件 0 层 (c=100) | 140,837 | 135,362 | −3.9% |
| 中间件 3 层 (c=100) | 136,302 | 137,465 | +0.9% |
| 中间件 10 层 (c=100) | 93,402 | 103,909 | +11.2% |
| SyncMW 3 层 (c=100) | 141,160 | 135,341 | −4.1% |
| SyncMW 10 层 (c=100) | 139,685 | 138,332 | −1.0% |
| 高并发 1000 | 63,077 | 91,620 | +45.3% |
| 高并发 10000 | 59,451 | 96,916 | +63.0% |
低并发场景有小幅下滑(Hello World 降了 15%)。具体是谁拖的没有做逐项 profiling,不好下定义——v2.6.3 到 v2.6.7 之间 HttpSessionImpl.cpp 的改动很大,加了好几条新分支(chunked body 检查、SSE 路径检测、IdleScanner Entry 注册),readBuf 也从连接级复用改成了每请求借还。这些在低并发 100 连接的场景下,单条请求本身已经够快了,任何一点点额外开销都会被放大。后面如果要追回这 15%,需要拿 perf 逐个排除。
但高并发是另一回事。1000 并发 +45%,10000 并发 +63%——这个幅度跑出来之前我自己也没想到。
中间件 10 层 +11%,提过消除转发协程帧把每请求 20 帧降到了 10 帧,但没单独对这个场景做 profiling,不确定是不是唯一的贡献因素。
v2.6.7 六框架重赛
下面是 2026-07-15 跑的三轮完整数据(平均)。跟上一版不同的是这版把六框架全都列出来了,包括 Crow、Oat++、cpp-httplib。
| 场景 | Hical | Drogon | Crow | Oat++ | cpp-httplib | Cinatra |
|---|---|---|---|---|---|---|
| Hello World (c=100) | 130,413 | 166,821 | 112,295 | 18,887 | 90 | 156,623 |
| JSON 响应 (c=100) | 141,285 | 100,758 | 101,720 | 16,597 | 90 | 140,979 |
| JSON Echo (c=100) | 132,890 | 78,583 | 75,736 | 13,156 | 90 | 156,924 |
| 路径参数 (c=100) | 140,555 | 92,162 | 88,135 | 16,920 | 90 | 154,054 |
| 中间件 0 层 (c=100) | 135,362 | 115,639 | 95,089 | 17,949 | 91 | 146,131 |
| 中间件 3 层 (c=100) | 137,465 | 106,197 | 94,106 | 17,425 | 91 | 148,269 |
| 中间件 10 层 (c=100) | 103,909 | 122,389 | 87,398 | 16,949 | 90 | 162,361 |
| SyncMW 3 层 (c=100) | 135,341 | 103,341 | 86,923 | 16,694 | 90 | 138,882 |
| SyncMW 10 层 (c=100) | 138,332 | 107,707 | 88,263 | 15,786 | 90 | 143,571 |
| 高并发 1000 | 91,620 | 91,348 | 71,506 | 7,409 | 92 | 81,495 |
| 高并发 10000 | 96,916 | 114,866 | 59,738 | 3,479 | 89 | 61,346 |
单位:Requests/sec,三轮平均值。加粗为该项最高。
延迟情况
| 场景 | Hical | Drogon | Cinatra |
|---|---|---|---|
| Hello World (c=100) | 1.94ms | 1.66ms | 1.68ms |
| 高并发 1000 | 10.87ms | 11.04ms | 12.73ms |
| 高并发 10000 | 59.83ms | 62.69ms | 81.37ms |
Avg Latency,三轮平均值。加粗为最低。
Hical 的 10K 延迟从上一版的 157ms 降到了 60ms(−62%),在三个框架里反而是最低的。1000 并发也是最低的 10.9ms。
高并发稳定性(10K timeout)
| 框架 | 第 1 轮 | 第 2 轮 | 第 3 轮 |
|---|---|---|---|
| Hical | 0 | 0 | 0 |
| Drogon | 1,640 | 0 | 0 |
| Cinatra | 2,079 | 812 | 604 |
| Crow | 0 | 0 | 0 |
| Oat++ | 11,530 | 9,593 | 15,434 |
| cpp-httplib | 24* | 24* | 24* |
*cpp-httplib 的 24 个 timeout 是其连接数上限所致,c=100 时一样有 24 个,跟高并发无关。
Hical 三轮全零 timeout 是这个版本最让我满意的地方。Drogon 第一轮炸了 1640 个,后两轮稳住了。Cinatra 三轮都有错误。Crow 是个意外惊喜:虽然 QPS 只有 60K,但三轮全零错误,比 Cinatra 稳。
这次的数据跟上一版能对上吗
基础场景(Hello World、JSON、路径参数、高并发)和 sync-filter 场景可以对上——Drogon 和 Cinatra 的版本没变,基础场景的 benchmark 代码也没变。Drogon 10K 上版 100K 这版 115K,波动在正常范围,说明测试环境本身没出幺蛾子。
但中间件场景(/middleware/3 和 /middleware/10)不能直接对比——上一版 Drogon 用的是 HttpFilter(同步回调链),这一版换成了 HttpCoroMiddleware(协程洋葱)。不是同一个测试,不能拿上一版的 Drogon 中间件数字跟这版比。Hical 自身的中间件提升(10 层 +11%)是同一套代码、同一个端点、只换版本,可比。
关于上一版结论的修正
回头看,上一版说"协程模型在高并发下有固有开销,Drogon 的回调模型更稳健"——v2.6.7 的数据表明这个结论下早了。优化完热路径之后,Hical 的 10K 延迟从 157ms 降到 60ms,反过来成了三个框架里最低的。至于哪些优化贡献了多少,没逐项 profiling 没法量化。
当然 Hello World 下滑 15% 这个没法回避,但原因不是单一因素能解释的,需要逐项 profiling 才能定位。后面拿 perf 排查一下,不过这个优先级不高——低并发场景 140K 还是 130K 对实际业务影响微乎其微,高并发场景的收益才是实打实的。
更新版总结
| 维度 | Hical v2.6.7 | Drogon | Cinatra |
|---|---|---|---|
| 常规吞吐 | 良好 | 优秀 | 优秀 |
| JSON 处理 | 快 (Boost.JSON) | 慢 (JsonCpp) | 快 (iguana) |
| 中间件 (SyncMW) | 最快 | 良好 | 模拟数据不可比* |
| 中间件 (协程洋葱) | 10 层有衰减 | 稳定 | 模拟数据不可比* |
| 高并发 1000 | 最好 | 持平 | 良好 |
| 高并发 10000 | 良好 | 最好 | 有衰减 |
| 高并发延迟 | 最低 60ms | 63ms | 81ms |
| 高并发稳定性 | 全零 timeout | 首轮有波动 | 三轮均有错误 |
| 内存占用 | 高 (200MB) | 中 (94MB) | 低 (57MB) |
*Cinatra 的中间件测试用的是 std::function 调用链模拟,不是框架原生的中间件机制,数字仅供参考。
v2.6.7 三轮原始数据
第一轮(2026-07-15 03:30)
| 场景 | Hical | Drogon | Crow | Oat++ | cpp-httplib | Cinatra |
|---|---|---|---|---|---|---|
| Hello World (c=100) | 134,420 | 175,877 | 108,685 | 20,375 | 90 | 158,187 |
| JSON 响应 (c=100) | 137,390 | 109,967 | 102,350 | 20,007 | 91 | 148,464 |
| JSON Echo (c=100) | 134,367 | 86,709 | 77,803 | 14,797 | 92 | 159,359 |
| 路径参数 (c=100) | 146,481 | 100,818 | 95,053 | 19,647 | 92 | 159,819 |
| 中间件 0 层 (c=100) | 142,329 | 117,764 | 99,669 | 19,467 | 91 | 148,070 |
| 中间件 3 层 (c=100) | 146,008 | 118,947 | 102,599 | 20,133 | 91 | 148,891 |
| 中间件 10 层 (c=100) | 102,936 | 121,190 | 92,445 | 18,579 | 89 | 164,013 |
| SyncMW 3 层 (c=100) | 140,262 | 102,412 | 76,653 | 17,125 | 91 | 137,787 |
| SyncMW 10 层 (c=100) | 144,414 | 116,962 | 93,215 | 16,132 | 91 | 153,012 |
| 高并发 1000 | 88,866 | 92,894 | 73,605 | 7,919 | 91 | 78,686 |
| 高并发 10000 | 93,166 | 114,537 | 53,354 | 2,644 | 90 | 58,717 |
第二轮(2026-07-15 04:08)
| 场景 | Hical | Drogon | Crow | Oat++ | cpp-httplib | Cinatra |
|---|---|---|---|---|---|---|
| Hello World (c=100) | 139,198 | 163,214 | 116,451 | 17,990 | 88 | 155,403 |
| JSON 响应 (c=100) | 144,283 | 100,428 | 96,150 | 15,186 | 90 | 138,290 |
| JSON Echo (c=100) | 138,493 | 72,901 | 78,326 | 11,918 | 89 | 153,243 |
| 路径参数 (c=100) | 142,118 | 85,468 | 71,255 | 15,908 | 90 | 157,474 |
| 中间件 0 层 (c=100) | 132,482 | 119,612 | 90,079 | 16,370 | 91 | 139,029 |
| 中间件 3 层 (c=100) | 135,869 | 78,170 | 79,170 | 14,233 | 90 | 148,299 |
| 中间件 10 层 (c=100) | 110,697 | 115,655 | 79,551 | 15,849 | 90 | 173,214 |
| SyncMW 3 层 (c=100) | 139,376 | 114,377 | 94,720 | 18,013 | 89 | 144,794 |
| SyncMW 10 层 (c=100) | 134,893 | 108,657 | 83,868 | 15,939 | 89 | 144,766 |
| 高并发 1000 | 89,268 | 85,628 | 79,274 | 7,253 | 92 | 79,392 |
| 高并发 10000 | 93,334 | 112,561 | 66,056 | 4,330 | 87 | 62,351 |
第三轮(2026-07-15 04:42)
| 场景 | Hical | Drogon | Crow | Oat++ | cpp-httplib | Cinatra |
|---|---|---|---|---|---|---|
| Hello World (c=100) | 117,620 | 161,374 | 111,750 | 18,295 | 91 | 156,280 |
| JSON 响应 (c=100) | 142,182 | 91,880 | 106,660 | 14,598 | 90 | 136,183 |
| JSON Echo (c=100) | 125,811 | 76,137 | 71,078 | 12,753 | 89 | 158,169 |
| 路径参数 (c=100) | 133,065 | 90,199 | 98,096 | 15,204 | 89 | 144,869 |
| 中间件 0 层 (c=100) | 131,275 | 109,542 | 95,521 | 18,011 | 92 | 151,296 |
| 中间件 3 层 (c=100) | 130,518 | 121,474 | 100,550 | 17,908 | 91 | 147,616 |
| 中间件 10 层 (c=100) | 98,095 | 130,323 | 90,199 | 16,420 | 89 | 149,857 |
| SyncMW 3 层 (c=100) | 126,384 | 93,234 | 89,396 | 14,943 | 90 | 134,065 |
| SyncMW 10 层 (c=100) | 135,688 | 97,501 | 87,705 | 15,288 | 90 | 132,935 |
| 高并发 1000 | 96,725 | 95,522 | 61,639 | 7,054 | 91 | 86,406 |
| 高并发 10000 | 104,246 | 117,500 | 59,803 | 3,463 | 88 | 62,971 |
更新于 2026-07-15 | Hical v2.6.7(含 #11 优化) | 其余框架版本不变 | 测试环境与 2026-05-25 完全一致
更多推荐


所有评论(0)