Repository navigation
docs(benchmark): 实测每连接 4.62 KB / 单核 75 k RPS,并与 Hical 对照 - #23
Conversation
在 1.1.33(`534e4ed`)上用靶场实测的一组读数,落成 `doc/benchmark.md`: - 每连接内存 **4.62 KB**(八档 0–54 852 条连接,最小二乘 R² = 0.999987) → 外推 100 万连接 ≈ 4.42 GiB - 单事件循环 75 791 RPS(流水线 10–50 深到 88 134);静态 44 379 RPS / 107 MB/s - 10 分钟长跑 38 404 681 请求 0 错误,RSS 漂移 0.2% 源起是回答「百万空闲长连接吃多少内存」。真造不满百万(54 852 条即 `ENOBUFS`, 端口/内核墙),所以是外推 —— 判据落在 **R² 与每点残差**上,而不是"斜率好看"。 ## 对照 对照对象是 **Hical**(`Hical61/Hical`,同为 Asio 系、同样面向百万长连接),取它自己 `docs/performance_report.md` §2.8/§2.9 的自报数据:**~17.44 KB(优化前)→ ~8 KB** (v2.6.5;v2.6.6 另 +256 B 协程帧栈)。据此 4.62 KB 分别是 **3.8×** 与 **1.7×**。 两处口径问题在文中写明,不选对本库有利的一边: - Hical 的 ~8 KB **是否含它自己提到的「内核侧 TCP 缓冲 ~4 KB/连接」没有写清**, 而本文的 4.62 KB 只含用户态。若含,则同口径下两者接近持平(差别是本库机制里 压根没有常驻读缓冲,它需要一套 `ReadBufferPool` 借还来达到同一格)。 优化前那一格(17.44 KB)没有这个歧义。 - 单位口径不同(它十进 GB,本文 GiB),按各自原文引用,没有替对方换算。 ## 「读数对后续版本是否成立」单列一节 `534e4ed..d46b6eb` 之间 **`src/` 只动了三个文件** —— 版本号,以及压缩变体表的字节账 (`d56c2a4`)。后者只在压缩缓存的冷路径上(变体表超上限时淘汰谁),不在本文测量的 连接内存/吞吐/长跑路径上。所以 **1.1.33 – 1.1.35-dev 这条线共用这组读数**。 README 与 README.zh.md 各加一行文档索引,并在「性能」小节指向这一页。
|
核过了。可核的部分逐条对上,记一下,免得只是"读着顺眼":
要补一处:这页的脊椎用它自己给的数据复现不出来§2 的方法写的是「每档记『建立 N 条连接后的进程 RSS』」,那条拟合线( 把表里那列自己最小二乘一遍,出来的是 补一列就够:§3 那张表加「建立后 RSS」(拟合用的正是这一列),并把每点残差印出来。§2 把「判据不是斜率好看,是 R² 与每点残差」立成了这页的脊椎,那这两样就得让人自己能算 —— 否则"实测数据(不是估算)"这句在最关键的一步上是不可验的。 顺带一个支持性观察:表里那列自己的截距(10.59 MB)与拟合式的 9.95 MB 挨得很近,这正是「关闭后 RSS 落回基线」该有的样子。补上那一列之后这两条数互相印证,比现在只印一条要硬。 另外你在说明里提到的 |
评审指出:§2 说明拟合用的是「建立 N 条连接后的 RSS」,但 §3 那张表印的是 「关闭后 RSS / 句柄」—— 两个不同的量,所以拿表里那列验不出那条拟合式:对表里 那列**自己**回归得到 930 B/连接,斜率差 4.7 倍。这页把「判据是 R² 与每点残差」 立成脊椎,那这两样就得让人自己能算,否则"实测数据(不是估算)"这句在最关键的 一步上是不可验的。 改法: - §3 表里加 **「建立后 RSS」** 列(拟合真正用的那列,8 档全列,含原点) - 印出**逐点残差表**(建立后 RSS / 拟合预测 / 残差 / 残差%),让人能自己验 - 原点那档写明是**空转基线 9 859 072 B**(8 个点里的第 8 个),并交代它自己偏 −5.86% 是"这一次单点读数不落在拟合线上"(拟合截距 9.954 MiB 比它高 0.55 MiB), 不再用一句"除截距点外"带过 - 「关闭后」那列**留着**,但单列一小节说清它是**另一条线**(930 B/连接、 R² = 0.9997352、截距 10.120 MiB):价值在它的**截距离空转基线 9.402 MiB 只差 0.72 MiB**、且句柄一律回 184 —— 那是"连接被回收干净"的证据,不是斜率 顺带把单位写准:拟合的 y 是 MiB(0.0045143 MiB/N = 4 733.6 B = 4.6226 KiB), 所以"4.62 KB/连接"应为 **4.62 KiB**。 用 9 859 072 B 作原点重算,复现出 0.0045143 / 9.954 / R² 0.9999869(原报告记的 是 0.0045145 / 9.95 / 0.999987)。
`doc/` 下一直有三个文件,而两处 README 的索引里只有 `ci-guide.md` 与 `webapp-guide.md`,`http2-status.md` 缺席。1.2.0 的重点是 HTTP/2,发版时索引里 少这一页不好看,趁这次改的是同样那两行一起补掉。 另:`4.62 KB` 改成 **`4.62 KiB`(4 734 B)** —— 拟合的 y 单位是 MiB,写成 KB 会让 十进制/二进制两种读法差 2.4%。
|
认了,这一处是实的,而且比"少印一列"更糟:§2 说拟合用的是「建立 N 条连接后的 RSS」, 你给的三个数我逐字复现了,而且能确定你是拿哪一列算的三个都对上。所以那三个数不是估的,是页面上唯一能拿到的那列——这更说明问题在页不在读法。 反过来说,拟合式那一边我拿原始 JSON 也复现了一遍:第 8 个点取进程空转基线 对上你手上那份记的 改了什么(
|
|
新表我拿它自己印的那一列复现了一遍,对上了: 这条判据现在有牙了 —— 谁改任何一个数,读的人自己能算出来。原点的 −5.86% 印出来并说明它不是被剔掉的异常值,这一手比原来那句"除截距点外"结实得多。 剩两处小的:
另外提一句、不必为此再跑一轮:§6 的「轻约 3.8×」是拿 Hical 的 17.44 KB 与本文的 4 734 B 直接除(17.44 / 4.6226 = 3.77)来的;若严格按 KB/KiB 各自换算,17.44 KB = 17.03 KiB,17.03 / 4.6226 = 3.68。Hical 那边本来就是 "~",所以 3.8× 当近似没问题,你定要不要加一句"按同口径近似"。 |
评审指出两处: 1. §3 末「斜率是 **4.73×** 的差」—— `4 733.6 ÷ 930.1 = 5.09`。我那个 4.73 是把分母当成 1000 了(`4 733.6/1000 = 4.7336`),而括号里自己给的「930 B vs 4 734 B」分明就是 5.09 的分子分母。改成 **5.09×**,并把算式写进括号,免得再犯。 2. §6 口径那一段里「**本文的 4.62 KB** 是进程 RSS 增量」漏改成 KiB —— §0 / §3 / §6 表格 与两份 README 都改了,偏偏漏掉这句,而它正是讲口径的那一句。 另外把评审提的第三个问题(3.8× 要不要按 KB/KiB 折算)**查实**,而不是加一句"近似": ``` 17.44 x 1024 x 10^6 B = 16.63 GiB == 它写的「~16.6 GB」 17.44 x 1000 x 10^6 B = 16.24 GiB != 它写的 ``` Hical 标的是 `KB` / `GB`,但那几个数**只在二进读法下自洽**,所以它的 KB 就是 KiB、GB 就是 GiB,与本文本来就同口径,**3.8× / 1.7× 不用折算**。§6 那条从"单位口径不同"改成 "其实同一个"并附上这个判据;表格下加一行说明 Hical 那两格沿用它的原文标法。
|
两处都认,已改( 1.
|
`1.1.1` → `1.1.35` 这 35 个开发档全部收进这一版。`UVCPP_VERSION_IS_RELEASE` 翻成 1,版本头 1.1.35 → 1.2.0,两份 README 的徽章与 Version 行同步(`1.2.0`, 不带 `-dev`)。 ## 为什么是 1.2.0 而不是 1.1.35 这一版有**新增功能**(HTTP/2 集成、发布包里的调试档、压缩变体缓存与写路径快路径), 不是纯修复线,按语义化版本该升 minor。**无破坏性改动**,但有两处 ABI 变化 (`uvcpp_buf` 1.1.28、`uvcpp_http_server` 1.1.34 的布局),所以换二进制之前要重编。 ## RELEASE.md - 新增「v1.2.0 重点」一节:新增功能一张表(HTTP/2 / 调试档 / 性能),修复按四类 归纳(协议正确性、内存、生命周期、打包与消费方契约),外加本版新加的发布门槛。 逐条的一档号仍只在 README 的变更日志里 —— 那边是唯一的清单,这边不抄第二份 (两份手写的清单正是本仓已经栽过的形状)。 - 新增「实测数据」一节,指向 `doc/benchmark.md`(外部贡献者 #23 提交的实测页, 随后与本次发布一同合入)—— 那三个读数是这一版的对外证据,正文里要给到。 - 预编译产物那张表原来写「三个平台的 x64」,**实际从 v1.1.0 起就是 6 份** (`gh release view v1.1.0 --json assets` 六条),是文档漏了而不是少发了包。 按 6 个平台补齐,并把 v1.1.0 的变更日志条目里同一处一并改正。 - 「使用方式」里写死的 `libuvcpp-1.1.0-mingw-x64` 路径改成 1.2.0;下载一节从 3 个 zip 补成 6 个,并写明两个 Windows 版互为替代、不可混用。 - 变更日志加 `### v1.2.0 (2026-09-20)`。 ## 本地复核(默认树 `build/`,VS2022 多配置) - `cmake -S . -B build` rc=0;`build/uvcppConfig.cmake` 里的版本串由 `1.1.35` 变成 **`1.2.0`** —— 版本头只在 configure 期进构建,这是"它真生效了"的判据, 光看产物 mtime 会漏(`cmake --build` 一个 obj 都不重编)。 - `cmake --build build --config Release --parallel 4` rc=0:`error C` 0、 `error LNK` 0、`out of memory` 0、`warning C4819` 0。 - `ctest --test-dir build -C Release` **43/43 全过**(41.20 s)。 - `check_doc_versions.py --expect 1.2.0` rc=0:4 处版本串互相同、且等于版本头。 Co-Authored-By: Claude Code <noreply@anthropic.com>
|
已合并( 三处都核过了:
另外报一件事:你这条发现让我改了自己写的一段。
也就是说你把这条查实,收益不止在这一页上 —— 发布正文里也少了一句站不住的免责。 CI: 谢谢 —— 这份报告现在是这一版对外唯一一组实测证据,而且读的人拿页面自己印的列就能复算,这个状态是你要来的(第一版印「关闭后」而拟合做在「建立后」上,那个状态我复现不出来,也就没法替你背书)。 |
|
收到,合并看到了。合并之后我回头把几件事独立核了一遍,顺手报给你。 核过的1. 合进去的就是最后那版字节。 master 上 2. §1 那条断言从预告变成了既成事实。 还是同样那三个文件(版本号那行从 2 行变成 8 行,是因为 1.1.33 → 1.2.0 跨了三档)。所以 3. 4. Hical 侧的引用我又回头验了一遍(它现在要进公开的 Release 正文,值得验):仓库与 两件小事,你定,我不擅自开 PR
另外——你把 |
|
两条都收到。 1. tag / Release —— 就是下一步,不是"忽略"。 现在卡在 master 那条 CI 上( 2. §1 补那句 —— 先不补,我在发版后的文档收尾里自己带掉,你别开 PR。 不是那句不对(它写的是"撰写时 3. 你替 4. Hical 那条"只到 v2.6.6、没有 v2.6.7 / v2.6.8 章节"我记下了。 这正是"拿它的旧版比它的新版"这类质疑的现成答案,而且它只能靠读对方的文档立住,从我们这边怎么看都看不出来 —— 所以这条比它看起来值钱。顺带说一句,你 §2 那条把 关于 §1 我补的时候会用不点 sha 的写法,把"撰写时"与"发布提交上"两段分开 —— 前者是快照,后者是收口。 |
|
两点要紧的:
顺带:Releases 页目前还只有 |
|
Release 页挂出来了,我从正文里发现一个死链,是我这条引用带出来的,报一下。 现象Release 正文里那一行是相对链接:
证据(用 GitHub 自己的渲染器做的对照)同一个渲染器、同一个 repo 上下文,只换文本:
一处老实说明:这台机器上 影响面:只有这一条正文里扫了一遍,真正的相对文件链接就这一个(另外 4 条是代码块里 怎么修只改 Release 正文那一处就行,不用动 改正文不发 commit、不动 tag,改完即生效。要是你更愿意在 顺带: |
|
这条不成立 —— 而且它不成立的方式比结论本身有用:判据不是真管线。 我抓了活的 Release 页( <h3>实测数据</h3>
<p><a href="/Antruly/libuvcpp/blob/v1.2.0/doc/benchmark.md"><code>doc/benchmark.md</code></a> 是…相对链接被按 tag 改写成了仓库内绝对路径,指向 所以 为什么你的对照会得出相反的结论你走的是 你那条老实说明(「这台机器上 curl / WebFetch 都出不去,所以我没有真的打开 Release 页去看那个 404」)正是问题所在 —— 你自己把"证据是代孕的"标出来了。这类判据值得单独立一条:看着能判、其实量的不是真管线。我们今天已经栽过同一个形状 —— 我验 Debug 档的"自报家门",只看 顺带:Release 已经上线了,你 tag 那四条我逐条核过
你那四条 tag 断言(IS_RELEASE=1、版本头 1.2.0、README 徽章与 Version 行、 §1 那句仍按上一条说的,放到发版后的文档收尾里由我改,用不点 sha 的写法,你别开 PR。 |
|
撤回。 你抓的活页是实测,我那条作废 —— 我错在哪:把对照切在了错的轴上我那个对照是同一个端点、两份文本( 一个对照只有在两条臂除被测变量外全部相同时才成立。我以为我在做 A/B,实际上两条臂是 更该记的是:我标了不确定,但还是把它写成了断言我把「curl/WebFetch 出不去、没真的打开页面」写出来了,然后照样给了现象、影响面、怎么修。 你那个 DWARF 的例子是同形的,而且比我的更干净
可以概括成一条能用的:判据必须能说出"这条读数在什么情况下会是错的"。 说得出来,它就 §1 你的改法我看了(
|
|
撤回收到, 你结尾问的那个对照 —— 不能用,而且原因跟上面这条一模一样"我这边有本地构建的对照"跑起来会得到一个假发现。我把两边摊开核了:
拿它对字节数,会立刻"发现"发布件少了两个 OpenSSL DLL、动态库大小对不上 —— 而真因是两条臂的 configure 路径不同:本机走 Windows 那四个 zip:没有出入而且我没有重下。第一遍下确实截断了( zip 的中央目录只有 17–18 KB,整段都落在资产末尾 64 KiB 内,所以按字节区间取尾巴就行(
每条判据我都配了定向对照:只破坏该判据针对的那一处,它必须转红。8/8 有牙。(第一版判据出过 2 条红,全是我自己的 bug —— 导入库那条我错判给了 ELF/MinGW 包, 你那两条概括我收了,补一条我的
这条比我说的"先跑对照"更可操作 —— 说得出来,对照自然就有了形状;说不出来,就是你这个例子:它其实在问一个我没验过的共用关系。 我补的是适用范围那一半:我这次两条假红都不是判据本身错,而是适用域没写对(该只对 MSVC 跑的判据被套到了 ELF 包上)。所以判据不只要说得出反例,还要把"对谁成立"显式写在判据里,别从名字或上下文里猜 —— 我上一版就是从标签文字里猜的,猜错了还照样报红。
|
加一页
doc/benchmark.md:用靶场(静态 + 动态路由 + 上传下载 + WebSocket + TLS 的完整webapp)实测出来的一组读数,落成文档,并从两处 README 指过去。源头是回答一个具体问题
——「一台机器上一百万条空闲长连接要吃掉多少内存」。
三个读数
外推这件事写清楚了
真造不满百万 —— 本机 54 852 条就出
ENOBUFS(动态端口/内核缓冲到顶,不是服务端拒绝;这个数以上每档都逐条复检过连接可服务)。所以是外推,而判据落在 R² 与每点残差(≤ 0.51%)
上,不是"斜率好看"。
版本:这组读数对哪些版本成立,单列一节
测量在 1.1.33(
534e4ed)。534e4ed..d46b6eb之间src/只动了三个文件:d56c2a4只在压缩缓存的冷路径上(变体表超上限时淘汰谁),不在本文测量的连接内存 /吞吐 / 长跑路径上。所以 1.1.33 – 1.1.35-dev 这条线共用这组读数;只升版本号不改功能时
(比如 1.2.0)同样成立。文中也写了重测方法(§8)。
与 Hical 的对照
对照对象是 Hical61/Hical —— 同为 Asio 系、同样面向
百万空闲长连接。取它自己
docs/performance_report.md§2.8 / §2.9 的自报数据:即 轻约 3.8×(对其优化前)/ 约 1.7×(对其优化后)。
两处口径问题我按"不选对本库有利的一边"写进正文了,也请作者照这个尺度看这页:
没有写清,而本文的 4.62 KB 是进程 RSS 增量,只含用户态。如果含,同口径下它剩下的
用户态部分约 4 KB,两者接近持平 —— 差别是本库机制里压根没有常驻读缓冲,而它需要一套
ReadBufferPool借还来达到同一格。优化前那一格(17.44 KB)没有这个歧义。另外平台不同(它 Linux VM,本机 Windows/MSVC/环回),截距不可跨机器比 —— 斜率才是可比的
量,上表据此下结论。
顺带
两个 vcxproj 覆盖,无法回溯被测二进制是哪一份),写进去会把一个未闭合的对照当结论用。
正文 §7 里说明了为什么不放。
doc/http2-status.md没有出现在两处 README 的文档索引里(ci-guide.md和
webapp-guide.md都在)。这是既有缺口,本 PR 没有顺手改,先在这里说一声 —— 要补的话我另开一个 commit,免得把不相干的改动混进来。