上一版测完总觉得哪里不对,于是改了配置又跑了一遍——这次结果老实多了。

前言:为什么重新测了一遍

这篇文章其实是第二版了。上一篇发出来之后我越想越不对劲——当时每个容器只给了 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 OSUbuntu 24.04.3 LTS Server
VM 分配16 GB RAM / 8 CPU
Docker Engine29.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 库
Hicalv2.6.3复制源码编译C++20 协程 (Boost.Asio)Boost.JSON
Drogonv1.9.8源码编译回调 (Trantor)JsonCpp
CinatralatestFetchContentC++20 协程 (自有实现)iguana

基础场景:Hello World / JSON

先看最简单的纯文本和 JSON 响应,这基本测的是框架自身的"底层开销"——HTTP 解析、路由匹配、响应序列化这些。

场景HicalDrogonCinatra
Hello World (GET /)153,871140,536165,717
JSON 响应 (GET /api/status)141,49995,090128,391
JSON Echo (POST /api/echo)123,58569,343131,440
路径参数 (GET /users/42)140,28586,487125,262

单位:Requests/sec(三轮平均值)

几个观察:

  1. Hello World:三者都在 14w~17w 的水平,Cinatra 166k 略高,Hical 154k,Drogon 141k。Cinatra 和 Hical 差距 8%,属于同一档;Drogon 稍低一些。

  2. JSON 序列化(GET /api/status):Hical 141k 领先,Cinatra 128k 紧随,Drogon 95k 落后较多。这里有个重要背景:Drogon 用的是 JsonCpp 库,而 Hical 用 Boost.JSON,Cinatra 用 iguana。JSON 库本身的性能差异直接体现在这里。

  3. JSON Echo(POST 反序列化+序列化):Cinatra 131k 略快于 Hical 124k,iguana 的反序列化确实快。Drogon 只有 69k,差距比较明显,大概率是 JsonCpp 的开销。

  4. 路径参数: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 时零协程帧,纯栈调用),其他框架继续用各自的原生机制。
场景HicalDrogonCinatra
0 层中间件140,837106,789132,121
3 层(原生)136,302105,171131,595
10 层(原生)93,402104,942137,830
3 层(Hical SyncMW)141,16094,588129,701
10 层(Hical SyncMW)139,685100,652127,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 连接

这是最能体现框架"韧性"的测试。

并发连接数HicalDrogonCinatra
100137,522145,663159,519
1,00063,07781,64669,797
10,00059,451100,53463,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 AvgDrogon AvgCinatra Avg
Hello World (c=100)1.91 ms1.71 ms1.77 ms
高并发 100014.94 ms12.28 ms15.37 ms
高并发 10000157.22 ms65.86 ms89.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 当前最大的短板,后面得好好优化。


资源占用

指标HicalDrogonCinatra
空载内存200.4 MB93.5 MB56.6 MB
满载内存197.0 MB93.5 MB55.8 MB
二进制大小2.1 MB1.9 MB528 KB
代码行数(bench)149 行234 行182 行

Hical 空载就吃了 200 MB,这是 PMR 内存池预分配的代价。好处是运行时几乎不再向系统要内存,坏处是启动就占这么大一坨。Drogon 94 MB,Cinatra 57 MB 最省。

有意思的是三者满载和空载内存几乎没变化——100 并发的压力下连接本身基本不额外吃内存。


综合评价

维度HicalDrogonCinatra
常规吞吐 (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)

场景HicalDrogonCinatra
Hello World (c=100)156,796.12127,980.96163,774.61
JSON 响应 (c=100)137,491.3674,466.33127,707.87
JSON Echo (c=100)111,098.9162,833.71135,691.72
路径参数 (c=100)150,121.5988,839.01118,966.68
中间件 0 层 (c=100)153,207.23112,848.48136,976.94
中间件 3 层原生 (c=100)136,898.55110,966.38138,247.89
中间件 10 层原生 (c=100)90,629.8290,728.35144,136.85
SyncMW 3 层 (c=100)136,901.79101,765.38124,463.02
SyncMW 10 层 (c=100)141,379.1999,782.52125,834.13
高并发 (c=100)110,907.90125,923.51166,994.13
高并发 (c=1000)62,416.4382,525.5173,922.75
高并发 (c=10000)59,184.0798,286.4670,999.25

高并发 10K Socket Errors: Hical timeout=1112, Drogon timeout=4022, Cinatra timeout=2719

第二轮(2026-05-25 10:55)

场景HicalDrogonCinatra
Hello World (c=100)165,965.45168,045.72165,510.87
JSON 响应 (c=100)145,883.87100,499.66135,568.09
JSON Echo (c=100)131,825.1881,832.00141,118.30
路径参数 (c=100)130,455.3577,424.95126,874.50
中间件 0 层 (c=100)138,603.0599,891.09121,208.64
中间件 3 层原生 (c=100)131,093.7490,841.49126,053.21
中间件 10 层原生 (c=100)93,569.44114,919.03141,083.12
SyncMW 3 层 (c=100)149,265.8580,593.01127,425.23
SyncMW 10 层 (c=100)135,342.8991,646.87133,061.49
高并发 (c=100)140,643.56149,802.87149,170.97
高并发 (c=1000)65,478.3986,809.8871,551.21
高并发 (c=10000)59,258.9494,281.8962,137.10

高并发 10K Socket Errors: Hical 无错误, Drogon 无错误, Cinatra timeout=919

第三轮(2026-05-25 11:42)

场景HicalDrogonCinatra
Hello World (c=100)138,851.55125,580.99167,866.51
JSON 响应 (c=100)141,122.19110,304.60121,898.13
JSON Echo (c=100)127,830.7163,364.29117,510.76
路径参数 (c=100)140,279.3193,197.92129,945.28
中间件 0 层 (c=100)130,700.88107,628.47138,178.82
中间件 3 层原生 (c=100)140,912.53113,704.01130,484.50
中间件 10 层原生 (c=100)96,007.00109,177.63128,268.96
SyncMW 3 层 (c=100)137,311.12101,405.52137,215.18
SyncMW 10 层 (c=100)142,333.72110,527.95124,236.71
高并发 (c=100)161,013.19161,261.49162,390.92
高并发 (c=1000)61,336.6875,602.2763,918.27
高并发 (c=10000)59,910.86109,031.9058,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.3v2.6.7变化
Hello World (c=100)153,871130,413−15.2%
JSON 响应 (c=100)141,499141,285−0.2%
JSON Echo (c=100)123,585132,890+7.5%
路径参数 (c=100)140,285140,555+0.2%
中间件 0 层 (c=100)140,837135,362−3.9%
中间件 3 层 (c=100)136,302137,465+0.9%
中间件 10 层 (c=100)93,402103,909+11.2%
SyncMW 3 层 (c=100)141,160135,341−4.1%
SyncMW 10 层 (c=100)139,685138,332−1.0%
高并发 100063,07791,620+45.3%
高并发 1000059,45196,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。

场景HicalDrogonCrowOat++cpp-httplibCinatra
Hello World (c=100)130,413166,821112,29518,88790156,623
JSON 响应 (c=100)141,285100,758101,72016,59790140,979
JSON Echo (c=100)132,89078,58375,73613,15690156,924
路径参数 (c=100)140,55592,16288,13516,92090154,054
中间件 0 层 (c=100)135,362115,63995,08917,94991146,131
中间件 3 层 (c=100)137,465106,19794,10617,42591148,269
中间件 10 层 (c=100)103,909122,38987,39816,94990162,361
SyncMW 3 层 (c=100)135,341103,34186,92316,69490138,882
SyncMW 10 层 (c=100)138,332107,70788,26315,78690143,571
高并发 100091,62091,34871,5067,4099281,495
高并发 1000096,916114,86659,7383,4798961,346

单位:Requests/sec,三轮平均值。加粗为该项最高。

延迟情况

场景HicalDrogonCinatra
Hello World (c=100)1.94ms1.66ms1.68ms
高并发 100010.87ms11.04ms12.73ms
高并发 1000059.83ms62.69ms81.37ms

Avg Latency,三轮平均值。加粗为最低。

Hical 的 10K 延迟从上一版的 157ms 降到了 60ms(−62%),在三个框架里反而是最低的。1000 并发也是最低的 10.9ms。

高并发稳定性(10K timeout)

框架第 1 轮第 2 轮第 3 轮
Hical000
Drogon1,64000
Cinatra2,079812604
Crow000
Oat++11,5309,59315,434
cpp-httplib24*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.7DrogonCinatra
常规吞吐良好优秀优秀
JSON 处理快 (Boost.JSON)慢 (JsonCpp)快 (iguana)
中间件 (SyncMW)最快良好模拟数据不可比*
中间件 (协程洋葱)10 层有衰减稳定模拟数据不可比*
高并发 1000最好持平良好
高并发 10000良好最好有衰减
高并发延迟最低 60ms63ms81ms
高并发稳定性全零 timeout首轮有波动三轮均有错误
内存占用高 (200MB)中 (94MB)低 (57MB)

*Cinatra 的中间件测试用的是 std::function 调用链模拟,不是框架原生的中间件机制,数字仅供参考。

v2.6.7 三轮原始数据

第一轮(2026-07-15 03:30)
场景HicalDrogonCrowOat++cpp-httplibCinatra
Hello World (c=100)134,420175,877108,68520,37590158,187
JSON 响应 (c=100)137,390109,967102,35020,00791148,464
JSON Echo (c=100)134,36786,70977,80314,79792159,359
路径参数 (c=100)146,481100,81895,05319,64792159,819
中间件 0 层 (c=100)142,329117,76499,66919,46791148,070
中间件 3 层 (c=100)146,008118,947102,59920,13391148,891
中间件 10 层 (c=100)102,936121,19092,44518,57989164,013
SyncMW 3 层 (c=100)140,262102,41276,65317,12591137,787
SyncMW 10 层 (c=100)144,414116,96293,21516,13291153,012
高并发 100088,86692,89473,6057,9199178,686
高并发 1000093,166114,53753,3542,6449058,717
第二轮(2026-07-15 04:08)
场景HicalDrogonCrowOat++cpp-httplibCinatra
Hello World (c=100)139,198163,214116,45117,99088155,403
JSON 响应 (c=100)144,283100,42896,15015,18690138,290
JSON Echo (c=100)138,49372,90178,32611,91889153,243
路径参数 (c=100)142,11885,46871,25515,90890157,474
中间件 0 层 (c=100)132,482119,61290,07916,37091139,029
中间件 3 层 (c=100)135,86978,17079,17014,23390148,299
中间件 10 层 (c=100)110,697115,65579,55115,84990173,214
SyncMW 3 层 (c=100)139,376114,37794,72018,01389144,794
SyncMW 10 层 (c=100)134,893108,65783,86815,93989144,766
高并发 100089,26885,62879,2747,2539279,392
高并发 1000093,334112,56166,0564,3308762,351
第三轮(2026-07-15 04:42)
场景HicalDrogonCrowOat++cpp-httplibCinatra
Hello World (c=100)117,620161,374111,75018,29591156,280
JSON 响应 (c=100)142,18291,880106,66014,59890136,183
JSON Echo (c=100)125,81176,13771,07812,75389158,169
路径参数 (c=100)133,06590,19998,09615,20489144,869
中间件 0 层 (c=100)131,275109,54295,52118,01192151,296
中间件 3 层 (c=100)130,518121,474100,55017,90891147,616
中间件 10 层 (c=100)98,095130,32390,19916,42089149,857
SyncMW 3 层 (c=100)126,38493,23489,39614,94390134,065
SyncMW 10 层 (c=100)135,68897,50187,70515,28890132,935
高并发 100096,72595,52261,6397,0549186,406
高并发 10000104,246117,50059,8033,4638862,971

更新于 2026-07-15 | Hical v2.6.7(含 #11 优化) | 其余框架版本不变 | 测试环境与 2026-05-25 完全一致


后续更新:修完环境之后重测(2026-09-28)

上一篇更新停在 2026-07-15 的 v2.6.7。这之后隔了两个多月,中间翻出来一个大问题——跟框架没关系,是测试环境本身。不先说清楚的话,下面那串数字没法解释。

先交代环境问题

宿主 Windows 的 Hyper-V / VBS 一直开着。这玩意儿开着的时候 VT-x 被 Hyper-V 独占,VirtualBox 拿不到硬件虚拟化,只能降级到 WHPX/NEM 慢路径——每次 VM exit 要走用户态 API 绕一圈,内存从一级地址翻译变成嵌套的两级翻译,中断还得先交给 hypervisor 再转发。对一个每秒上百万次 syscall、上百万次中断的网络密集负载来说,这个代价接近一个数量级。

自查的方法很简单,宿主机上敲一句:

(Get-CimInstance Win32_ComputerSystem).HypervisorPresent

返回 True 就是中招了。

修复做了四件事:关掉宿主的 Hyper-V / VBS(bcdedit /set hypervisorlaunchtype off 后重启);网卡从 VirtualBox 默认的 e1000(Intel 82540EM,全模拟网卡,每个包都要走多次 VM exit 加寄存器级模拟)换成 virtio-net(半虚拟化,走 virtqueue 批量);半虚拟化接口设成 KVM、启用 PAE/NX;SATA 控制器勾选「使用主机 I/O 缓存」、虚拟盘标记为 SSD。前两条是大头,后两条加起来又添了约 1.8 倍。

修完前后不是小修小补:同一份二进制、同一套脚本,Hical 的 Hello World 从 13 万 QPS 变成 53 万,差了 4 倍多。

所以上面所有更新段的数据全部作废——包括 2026-05-25 那版和 v2.6.7 那版。那不是在测框架,是在测 hypervisor,4 倍的噪声压在上面,什么结论都是假的。

修完之后我连着跑了两轮。第一轮的数字和复核那轮对不上:1000 并发三家普遍差一到三成,10000 并发 Hical 的 Max 差了 5 倍(445ms vs 83ms)。同一个框架、同一套脚本跑出两种结果,我判断是前一轮采样本身有问题,所以本文以复核后的这版数据为准。

顺手留个自检清单,压测前跑一遍:宿主 HypervisorPresent 必须返回 False;VM 网卡必须是 virtio-net 而不是 e1000;另外,一轮结果里要是出现了连你自己都觉得不对劲的数,别急着当异常值挑掉,先回头看看整轮是不是有问题。

基础场景:Hello World / JSON

场景HicalDrogonCinatra
Hello World (GET /)562,810515,760605,123
JSON 响应 (GET /api/status)534,547361,592567,065
JSON Echo (POST /api/echo)439,399292,815533,886
路径参数 (GET /users/42)516,732335,876538,086

单位:Requests/sec。加粗为该项最高。

  1. Hello World:Cinatra 605K 第一,Hical 563K 第二(差 7.5%),Drogon 516K 第三(差 9.1%)。三家还是同一档。

  2. JSON 序列化(GET /api/status):Cinatra 567K、Hical 535K 咬得很紧,Drogon 362K 掉了 30%。背景还是那个:Drogon 用 JsonCpp,Hical 用 Boost.JSON,Cinatra 用 iguana。JSON 库的差距在这个场景里比框架的差距大得多。

  3. JSON Echo(POST 反序列化+序列化):Cinatra 534K、Hical 439K,Drogon 293K(掉 43%)。jsoncpp 的反序列化比序列化更贵,这是它最难看的一个场景。

  4. 路径参数:Cinatra 538K、Hical 517K,Drogon 336K。Drogon 的参数路由走正则,代价在这里挺直接。

Cinatra 四个场景全第一,Hical 四个场景全第二,领先幅度 4.1%~21.5%。

这里补一个必须说清楚的口径差:三家返回的 body 一模一样(13 字节的 Hello, World!),但实际推上网络的字节数差很多。用 Transfer/sec ÷ Requests/sec 反推每请求字节:Hical 175 B、Drogon 151 B、Cinatra 106 B。Hical 每请求比 Cinatra 多推 69 字节,整轮多发 65% 的数据——主要是 Hical 会自动附带 Server / Connection / Date 三个响应头,Cinatra 默认不发 Date。这个算法可以拿 wrk 日志里的 GB read 交叉验证:Hical 那行写着 16940788 requests / 2.76GB read,除一下正好 174.9 B。

所以"Hical 比 Cinatra 慢 7.5%"这个说法得打个折:论单位带宽效率,Hical 反而是高的(93.93MB/s vs 61.17MB/s)。如果瓶颈是出网带宽,这 69 字节就是实打实的成本;如果瓶颈是每秒能处理多少请求,那 7.5% 就是实打实的差距。

高并发:1000 和 10000 连接

并发连接数HicalDrogonCinatra
100562,810515,760605,123
1,000447,487464,828479,291
10,000181,924197,957197,470

单位:Requests/sec。加粗为该项最高。

相对 c=100 的衰减:

并发连接数HicalDrogonCinatra
1,000-20.5%-9.9%-20.8%
10,000-67.7%-61.6%-67.4%
  • 1000 并发:Hical(-20.5%)和 Cinatra(-20.8%)一起掉两成,Drogon 只掉 9.9%,三家 46 万~48 万咬得很紧。

  • 10000 并发:Drogon 198K 第一,Cinatra 197K 跟它只差 0.2%,Hical 182K 第三,三家差距不到 9%。

错误率这块变化最明显。早先几版三家都在报 timeout(Hical 1112、Drogon 4022、Cinatra 2719 那种量级),因为那会儿容器有效 fd 只有一千出头,所谓一万并发根本没压进去。nofile 提到 65536 加宿主内核参数调优之后:

并发 10K 稳定性HicalDrogonCinatra
Socket timeout002,288

Hical 和 Drogon 全零错误,Cinatra 剩下 2288 个 timeout。它 QPS 仍有 197K,说明稳态吞吐没问题,问题出在那一小撮慢请求上——下一节就看得出来。

延迟对比

场景HicalDrogonCinatra
Hello World (c=100)174.11us195.54us149.52us
高并发 10002.24ms2.12ms2.05ms
高并发 1000031.56ms29.49ms35.09ms

Avg Latency。加粗为最低。

c=100 时三家都在 200us 以内。往上拉,c=1000 三家都在 2.0~2.3ms;c=10000 是 Drogon 29.49ms、Hical 31.56ms、Cinatra 35.09ms,也就差 19%。

不过平均延迟只讲了半个故事,尾延迟才是生产环境里的真杀手:

并发 10KHicalDrogonCinatra
Max 延迟83.39ms94.41ms2.00s
Stdev10.14ms10.00ms21.43ms

Hical 和 Drogon 的 Max 都在 100ms 以内(83ms / 94ms),Cinatra 直接飙到 2.00 秒,Stdev 也是三家里最高的。结合前面那 2288 个 timeout,基本可以判断它在万级连接下存在少量请求严重卡顿——平均延迟这个指标把这个问题完全掩盖了。

只看 Avg 延迟做选型,会被 Cinatra 的 35ms 骗过去。

资源占用

指标HicalDrogonCinatra
空载内存129.6MiB97.79MiB92.46MiB
满载内存129.6MiB92.05MiB86.87MiB
二进制大小3.0MB1.9MB508KB
代码行数(bench)95 行67 行84 行

Hical 空载就吃 129.6MiB,是三家最高的,比 Drogon 高 33%——这是 PMR 三层内存池预分配的代价。好处是运行时不怎么再向系统要内存(空载满载一个数),坏处是启动就占住这一坨。Cinatra 92MiB、Drogon 98MiB 都在百兆以内。

Drogon 和 Cinatra 的满载数字比空载低一点,是 docker stats 采样时刻的 RSS 波动,不是真把内存还回去了。

二进制 Hical 3.0MB 最大,因为它把 Boost.Asio / Boost.JSON / Boost.System 全静态链进去了;Cinatra 是 header-only,508KB。代码行数那栏,Hical 的 95 行里有 36 行是中间件端点(只给我自己跟踪中间件开销用),扣掉之后 59 行,这栏看看就好。

综合评价

维度HicalDrogonCinatra
常规吞吐 (c=100)优秀 (56w)良好 (52w)最好 (61w)
JSON 处理快 (Boost.JSON)慢 (JsonCpp)快 (iguana)
高并发 1000良好 (45w)良好 (46w)最好 (48w)
高并发 10000一般 (182K)最好 (198K)良好 (197K)
高并发平均延迟31.56ms最低 29.49ms最高 35.09ms
高并发尾延迟最好 83.39ms94.41ms最差 2.00s
高并发稳定性全零 timeout全零 timeout2288 timeout
内存占用高 (130MB)中 (98MB)低 (92MB)

这一版推翻了什么

第一版里那句"协程模型下每个连接维护一个协程帧,一万个帧的管理和调度本身就是开销,Drogon 的回调模型不存在这个问题"——方向还在(Hical 在 10K 下确实还是第三),但量级完全不是一回事:当时是 59K vs 101K,差 1.7 倍;现在是 182K vs 198K,差 8%。当时的"被按在地上"是环境噪声放大的结果。

v2.6.7 那版反过来写的"优化完热路径之后 Hical 成了延迟最低的",同样站不住。环境修干净之后,Hical 的 10K 平均延迟是三家第二(31.56ms,比 Drogon 高 7%),QPS 第三。

但尾延迟这块 Hical 是站得住的:10K 时 Max 83.39ms、Stdev 10.14ms,两项都是三家最好。反过来 Cinatra 的尾巴是 2.00s——上一版它这个数字被平均值盖住了,没看出来。

所以现在的判断是:Hical 在万级连接下的吞吐不如 Drogon 和 Cinatra,但延迟分布比 Cinatra 干净得多。至于吞吐差的那 8% 具体差在哪,没做 profiling,我不编原因。

几点坦诚的说明

利益相关:我是 Hical 的作者,这个测试从设计到执行都是我一个人干的。Docker 隔离、相同资源限制、wrk 统一配置——这些我尽力做到了,但屁股决定脑袋,测试设计上的视角偏差肯定有。对 Hical 不利的数据我一样贴了:四个基础场景全第二、10K 并发第三、内存占用最高。

这几轮测下来,翻出来的分别是容器限制、虚拟化环境和采样流程,没一次是框架的问题。数据对不上还硬发不像话,所以每一版都摊开写,让读者知道哪个数被换过、为什么换。

中间件场景这次没测:之前测过 0/3/10 层协程洋葱链,但比到后面发现只有 Hical 和 Drogon 有真正等价的运行时协程中间件,Cinatra 只能用 std::function 手搓调用链模拟——拿模拟的数字和真机制并排比没有意义,所以整块没放进来。要测中间件开销的话,建议只做 Hical 和 Drogon 这一对。

选框架不能只看 QPS:Drogon 有完整的 ORM、WebSocket、HTTP 客户端和成熟社区;Cinatra 轻巧、header-only、吞吐强;Hical 在中间件灵活度和 JSON 上有自己的想法。性能只是选型的一个维度,生态、文档、长期维护成本这些才是用下去绕不开的。

本次数据来源

本节的 QPS、延迟、错误率数据在 benchmark/output/results.md,内存/二进制/代码行数在 benchmark/output/stats.md,复现步骤参照 benchmark/README.md。


更新于 2026-09-28 | Hical main @ ba28ad0 | 环境:宿主 Hyper-V 关闭 + virtio-net(此前所有版本的测试环境作废)


附:HttpArena 上的成绩

前面所有数字都是我在自己 VM 上用 wrk 跑的——4 核容器、30 秒一轮。这套数据适合自己跟自己比、跟同一台机器上的其他框架比,但拿出去跟别人对不上,因为环境和并发模型都不一样。

想看 Hical 在别人家的机器上跑成什么样,可以去 HttpArena。这是个开源的 HTTP 框架基准测试平台,用来接替已经归档的 TechEmpower 那套。硬件是一台 AMD Ryzen Threadripper PRO 3995WX 的 64 核机器(251GB 内存,Ubuntu 24.04.4,非云 VM、没有噪声邻居),27 个 profile 覆盖 HTTP/1.1、HTTP/2、HTTP/3、WebSocket、数据库、TLS、反向代理这些。跑分前每个框架得先过一套验证(含随机输入和硬编码检测),框架通过 PR 贡献进去。

Hical 在上面声明了 14 个 profile。下面的数字取自 HttpArena 仓库 main 分支的 site/data/results/,hical 那份最后一次更新是 2026-09-25 的提交 b959d4ee。榜单是活的,要看最新的自己点进去。

先搞清楚榜单怎么排

榜单不是把所有框架拉一张大表排,而是按 type 分组:flagship + emerging 是默认视图,另外三组是 engine、experimental、infrastructure,各组算各组的。Hical 的 type 是 experimental,所以直接打开榜单默认看不到它,得把类型筛选切到 experimental。

这一组现在有 9 个实现:araara、araara-standard、fib、fib-tuned、fib-websocket、hical、nilo、swerver、swerverts。

组内成绩

同一个 profile 不是每个实现都跑,所以分母不一样:

ProfileHical RPS组内名次组内第一
echo-ws-5123,614,2221/3Hical
echo-ws-163843,287,4021/3Hical
echo-ws-limited-5121,919,8291/3Hical
echo-ws-limited-40962,275,2641/3Hical
echo-ws-40963,676,9142/3nilo
baseline-40963,228,0432/6swerver
limited-conn-40962,407,6812/6swerver
async-db-1024136,2232/5swerver
async-320001,682,3053/5nilo
json-tls-4096549,7653/5swerver
json-comp-4096261,9553/5swerver
echo-ws-pipeline-5124,323,9303/3nilo

四个 WebSocket 项拿了组内第一。不过得说明白:这几项组内只有 3 个实现跑了,分母不大,这个"第一"的含金量有限。

放到全站看

组内第一归组内第一,但如果问"同一项全世界跑最快的是谁",答案基本都在别的组里。下面这个对照是我自己拉的,榜单本身不跨组排名:

ProfileHical RPS同项最快倍数
limited-conn-40962,407,681edixoi 2,739,1411.14×
echo-ws-limited-40962,275,264tokio-websockets 2,641,8011.16×
echo-ws-limited-5121,919,829zix 2,254,5041.17×
echo-ws-5123,614,222ringzero-ws 4,353,1471.20×
echo-ws-40963,676,914zix 4,469,9141.22×
echo-ws-163843,287,402zix 4,254,8431.29×
baseline-40963,228,043zix 4,442,3291.38×
async-320001,682,305ioxide 2,782,5541.65×
async-db-1024136,223zix 400,4442.94×
json-tls-4096549,765zix 1,845,2483.36×
json-comp-4096261,955zix 1,046,5814.00×
pipelined-40964,851,267zix 58,761,61612.11×
echo-ws-pipeline-5124,323,930ringzero-ws 66,683,70215.42×

管线化这两项最扎眼。 pipelined 和 echo-ws-pipeline 的绝对值都在 400 万 RPS 以上,放在多数组里都能打,可同项最快的是 5800 万和 6600 万——差了十几倍。管线化这块 Hical 基本没专门做优化,差距是实打实的,不是口径问题。

json-comp 和 json-tls 落后 3~4 倍,async-db 落后 2.9 倍。这几项没什么好辩解的,就是没优化到位。

另外三个 latency-* 是固定速率测延迟的 profile,不按 RPS 排名。Hical 在这三项都达标了:latency-1m 跑满 100 万 req/s,latency-10k 跑满 1 万,latency-500k-8cpu 达到目标的 86%。

几句必须说清楚的话

第一,这两套数字不能互相换算。 HttpArena 是 64 核裸机、5 秒一轮;本文是 4 核容器、30 秒一轮。Hical 在 HttpArena 上 baseline 是 322 万 RPS,本文里 Hello World 是 56 万——差 5.7 倍。这个差距里没有一分是"优化出来的",全是硬件和口径的差别。别把两边混着看。

第二,榜单数字现在是可以回溯的。 Hical 的 Dockerfile 里 ARG HICAL_REF 锁的是一个具体 commit(5cc47672),不是 main。早前有段时间用的是 main,那会儿"榜单上这个分数对应哪个 commit"根本说不清;现在每个分数都能对上确切的代码版本,代价是每次更新都得提 PR。

第三,Hical 的类型是 experimental。 这是平台按定位打的分类,不用美化——它确实还在早期。

第四,榜上强的那几项和弱的那几项,跟本文的结论能互相印证。 本文测出来 Hical 常规吞吐排第二、万级连接下的尾延迟控制得还行;HttpArena 上也是 WebSocket 和基础吞吐拿得出手,管线化和 json-comp 掉队。后面这块本文根本没覆盖到,算是补上了一块盲区。两边合起来看,至少能说明我不是在自家 VM 上把数据测歪了。

复现入口:HttpArena 仓库|在线榜单|Hical 的接入配置在该仓库的 frameworks/hical/ 下,bench 服务器源码在 Hical 仓库的 docker/HttpArena/

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐