Skip to content

docs(benchmark): 实测每连接 4.62 KB / 单核 75 k RPS,并与 Hical 对照 - #23

Merged
Antruly merged 4 commits into
Antruly:masterfrom
sercebr:docs/benchmark
Sep 20, 2026
Merged

Antruly merged 4 commits into
Antruly:masterfrom
sercebr:docs/benchmark

Conversation

@sercebr

@sercebr sercebr commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator

加一页 doc/benchmark.md:用靶场(静态 + 动态路由 + 上传下载 + WebSocket + TLS 的完整
webapp)实测出来的一组读数
,落成文档,并从两处 README 指过去。源头是回答一个具体问题
——「一台机器上一百万条空闲长连接要吃掉多少内存」。

三个读数

值
每连接内存 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² 与每点残差(≤ 0.51%)
上,不是"斜率好看"。

版本:这组读数对哪些版本成立,单列一节

测量在 1.1.33(534e4ed)。534e4ed..d46b6eb 之间 src/ 只动了三个文件:

src/uvcpp/uvcpp_version.h      |  2 +-     ← 版本号
src/web/uvcpp_http_server.cpp  | 31 +++--  ← 压缩变体表的字节账(d56c2a4)
src/web/uvcpp_http_server.h    | 10 +++-

d56c2a4 只在压缩缓存的冷路径上(变体表超上限时淘汰谁),不在本文测量的连接内存 /
吞吐 / 长跑路径上。所以 1.1.33 – 1.1.35-dev 这条线共用这组读数;只升版本号不改功能时
(比如 1.2.0)同样成立。文中也写了重测方法(§8)。

与 Hical 的对照

对照对象是 Hical61/Hical —— 同为 Asio 系、同样面向
百万空闲长连接。取它自己 docs/performance_report.md §2.8 / §2.9 的自报数据:

实现 每空闲连接 百万连接
Hical v2.6.4(优化前) ~17.44 KB ~16.6 GB
Hical v2.6.5 / v2.6.6 ~8 KB(v2.6.6 再 +256 B) ~7–8 GB
libuvcpp 1.1.33 4.62 KB 4.42 GiB

即 轻约 3.8×(对其优化前)/ 约 1.7×(对其优化后)。

两处口径问题我按"不选对本库有利的一边"写进正文了,也请作者照这个尺度看这页:

  1. Hical 的 ~8 KB 是否含它自己在那句话里提到的「内核侧 TCP 缓冲 ~4 KB/连接不可优化」
    没有写清
    ,而本文的 4.62 KB 是进程 RSS 增量,只含用户态。如果含,同口径下它剩下的
    用户态部分约 4 KB,两者接近持平 —— 差别是本库机制里压根没有常驻读缓冲,而它需要一套
    ReadBufferPool 借还来达到同一格。优化前那一格(17.44 KB)没有这个歧义。
  2. 单位口径不同(它十进 GB,本文 GiB),按各自原文引用,没有替对方换算。

另外平台不同(它 Linux VM,本机 Windows/MSVC/环回),截距不可跨机器比 —— 斜率才是可比的
量
,上表据此下结论。

顺带

  • 这一页故意没有把 TLS 那组放进来:TLS 相对旧基线的差目前归因未闭合(同一输出路径被
    两个 vcxproj 覆盖,无法回溯被测二进制是哪一份),写进去会把一个未闭合的对照当结论用。
    正文 §7 里说明了为什么不放。
  • 改 README 时注意到 doc/http2-status.md 没有出现在两处 README 的文档索引里(ci-guide.md
    和 webapp-guide.md 都在)。这是既有缺口,本 PR 没有顺手改,先在这里说一声 —— 要补的话
    我另开一个 commit,免得把不相干的改动混进来。

在 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 各加一行文档索引,并在「性能」小节指向这一页。
@Antruly

Antruly commented Sep 20, 2026

Copy link
Copy Markdown
Owner

核过了。可核的部分逐条对上,记一下,免得只是"读着顺眼":

  • §1 那句「534e4ed..d46b6eb 之间 src/ 下只动了三个文件」精确成立:git diff --stat 534e4ed..d46b6eb -- src/ 出来就是 2 / 31 / 10 三个数,与文中那张图逐字相同;同期另外 10 个文件全在 src/ 外(文档、CI、测试、打包)。
  • d56c2a4 的落点也确认了:改动只在 compress_variant_evict() 那个 while 循环与 compress_variant_stats() 上,确实只在变体表超上限那条冷路径。
  • §6 引 Hical 的每一处都对上了原文(docs/performance_report.md §2.8 / §2.9):~7.5 KB、~2 KB、合计 ~17.44 → ~8 KB、~16.6 GB → ~7–8 GB、§2.9 的 +256 B 与 ↓71.8%,连「内核侧 TCP 缓冲 ~4 KB/连接不可优化」确实就在同一句里这一点也是准的。你把它当口径歧义写进正文、不单选对本库有利的那个读法,是对的做法。

要补一处:这页的脊椎用它自己给的数据复现不出来

§2 的方法写的是「每档记『建立 N 条连接后的进程 RSS』」,那条拟合线(0.0045145 × N + 9.95、R² = 0.999987、每点残差 ≤ 0.51%)也是对着这条序列算的。但 §3 那张表印的是「关闭后 RSS / 句柄」—— 两个不同的量。于是拿表里那列去验那条线,差得很远:

N =  8000   表里 18.2 MB   拟合式预测  46.1 MB   (差 +27.9)
N = 48000   表里 55.3 MB   拟合式预测 226.6 MB   (差 +171.3)

把表里那列自己最小二乘一遍,出来的是 k = 0.0009302 MB/N(0.95 KB/连接)、b = 10.59 MB、R² = 0.999749 —— 斜率差 4.9 倍,R² 也不是印的那个 0.999987。读这页的人只会得出两种结论之一:那条拟合线是对一组没印出来的数据算的,或者斜率写错了。前者才是事实,但页面上看不出来。

补一列就够:§3 那张表加「建立后 RSS」(拟合用的正是这一列),并把每点残差印出来。§2 把「判据不是斜率好看,是 R² 与每点残差」立成了这页的脊椎,那这两样就得让人自己能算 —— 否则"实测数据(不是估算)"这句在最关键的一步上是不可验的。

顺带一个支持性观察:表里那列自己的截距(10.59 MB)与拟合式的 9.95 MB 挨得很近,这正是「关闭后 RSS 落回基线」该有的样子。补上那一列之后这两条数互相印证,比现在只印一条要硬。

另外

你在说明里提到的 doc/http2-status.md 不在两处 README 的文档索引里 —— 确实是既有缺口,ls doc/ 出来是三个文件,索引里只有两个。你改的正好就是那两行,顺手在同一个 PR 里补掉吧(另开一个 commit 也行,不必为此新开一个 PR):1.2.0 的重点就是 HTTP/2,发版时索引里少一页不好看。

评审指出:§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%。
@sercebr

sercebr commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator Author

认了,这一处是实的,而且比"少印一列"更糟:§2 说拟合用的是「建立 N 条连接后的 RSS」,
§3 那张表印的是「关闭后 RSS / 句柄」—— 页面上给出的唯一一列恰好不是拟合用的那一列。
按要求"判据是 R² 与每点残差"来读这页的人,到这一步只能算出个别的数。

你给的三个数我逐字复现了,而且能确定你是拿哪一列算的

对印出来的那列(1 位小数)回归:k=0.0009302  b=10.587  R²=0.9997488
你给的:                        k=0.0009302  b=10.59   R²=0.999749

三个都对上。所以那三个数不是估的,是页面上唯一能拿到的那列——这更说明问题在页不在读法。
(另外两个候选我都试过并排除:用 opened=54852 的原值算,k=0.0009301 / b=10.612 /
R²=0.9997352;用 target=55000 算,0.0009283 / 10.649 / 0.9997894。都不是你的数。)

反过来说,拟合式那一边我拿原始 JSON 也复现了一遍:第 8 个点取进程空转基线
9 859 072 B
(= 9.402 MiB),8 点回归出来

k = 0.0045143 MiB/N   b = 9.954 MiB   R² = 0.9999869

对上你手上那份记的 0.0045145 / 9.95 / 0.999987。所以那三个常数本身没问题,
缺的是能让人走到它的那一列 —— 原点那一档原来在页面上根本不存在。

改了什么(2c6b6db)

  • §3 表加 「建立后 RSS」 列,8 档全列(含原点那档写明是基线 9 859 072 B)。
  • 加逐点残差表:建立后 RSS / 拟合预测 / 残差 / 残差%,让人自己能验。
  • 原点那点自己偏 −5.86%(拟合截距 9.954 MiB 比它高 0.55 MiB)—— 原先一句
    "除截距点外"带过,读者不知道偏多少,现在印出来并说明它是"这一次单点读数不落在
    拟合线上",不是被剔掉的异常值。
  • 残差在零点附近交替、没有走向,这一点也从表里看得出来了。

你那个支持性观察我采纳了,而且它现在有了名字:「关闭后」那一列单列一小节,写明它
是另一条线(930 B/连接、R² = 0.9997352、b = 10.120 MiB),两个数都对但量的不是
同一件事 —— 要判容量看「建立后」那条线。而它自己的价值在截距: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(4 734 B),两处 README 一起改。KB/KiB 差 2.4%,
    放在"每连接"这个乘 10⁶ 的数上不该含糊。
  • doc/http2-status.md 已按你说的在同一个 PR 里补掉,单独一个 commit(6cb2ca8),
    两处 README 的索引都加上了。你说的对:1.2.0 的重点就是 HTTP/2,发版时索引里少这一页
    不好看。

head = 6cb2ca8。CI 上一次 15 绿、3 个 windows 档还在跑,这次推完我再看一遍。

@Antruly

Antruly commented Sep 20, 2026

Copy link
Copy Markdown
Owner

新表我拿它自己印的那一列复现了一遍,对上了:

8 点回归:k=0.0045143  b=9.953(印 9.954,四舍五入)  R²=0.9999869
每连接:  4 733.6 B = 4.6226 KiB
残差:    8 个逐个与「逐点残差」表相同(原点 −0.551 / −5.86%;
          8000 那点我 +0.209、印 +0.210,差在四舍五入;其余逐位一致)
外推:    4.4085 + 0.0097 = 4.4182 GiB
关闭后那条线:0.0008870 MiB/N = 930.1 B、b=10.120、R²=0.9997354(印 0.9997352)

这条判据现在有牙了 —— 谁改任何一个数,读的人自己能算出来。原点的 −5.86% 印出来并说明它不是被剔掉的异常值,这一手比原来那句"除截距点外"结实得多。

剩两处小的:

  1. 4.73× 应是 5.09×(doc/benchmark.md:138)。4 733.6 / 930.1 = 5.089;而 4 733.6 / 1 000 = 4.734 —— 看着像分母用了 1000。括号里你自己给的「930 B vs 4 734 B」就是 5.09 的分子分母,所以只差这个比值本身。

  2. :229 那句仍是 4.62 KB。§0 / §3 / §6 与两份 README 都改成 KiB 了(连「4 734 B」都标了),只剩这一处留在 KB —— 而它恰好是「本文的 4.62 KB 是进程 RSS 增量」这句,也就是你在讲口径的那一句。

另外提一句、不必为此再跑一轮:§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 那两格沿用它的原文标法。
@sercebr

sercebr commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator Author

两处都认,已改(461cfa8)。

1. 4.73× → 5.09×

4 733.6 ÷ 930.1 = 5.0893。我那个 4.73 是把分母当成 1000 了(4 733.6 / 1000 = 4.7336),
而括号里自己写的「930 B vs 4 734 B」分明就是 5.09 的分子分母 —— 也就是说同一句话里
的括号和自己矛盾
,你只看了一眼就看出分母不对。现在把算式写进括号(4 733.6 ÷ 930.1),
免得下次再靠猜。

2. 末处的 4.62 KB

确实漏了。§0 标题、§3 拟合式、§3 逐点那句、§6 表格、两份 README 全改成 KiB 了(连
「4 734 B」都标了),偏偏漏掉这一句 —— 而它正是讲口径的那一句,也就是最不该含糊的
位置。已改。

3. 3.8× 要不要折算 —— 我查了,不用折算,而且能证明

你提这条时说"不必为此再跑一轮",但这个问题不用跑测试,用你手上那份 Hical 的数字就能判死:

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   != 它写的
8     x 1024 x 10^6 B =  7.63 GiB   == 它写的「~7-8 GB」

Hical 标的是 KB / GB,但它的百万连接那一格只在二进读法下自洽 —— 也就是说它的
KB 本来就是 KiB、GB 本来就是 GiB,只是标签写成了十进的样子。所以两边本来就
同口径
,17.44 / 4.6226 = 3.77 → 3.8× 直接成立,不必先折成 17.03 KiB 再除。

于是 §6 那一条我从"单位口径不同"改成了"其实同一个",并把这个判据(16.63 vs 16.24,对上它自己写的 ~16.6)附上 —— 它现在是能验的,而不是一句"两边不一样,所以
我不替对方换算"。表格下面另加一行,说明 Hical 那两格沿用它的原文标法。

坦白说这条原来被我写反了:我把它当成"两种读法都列出来、不选有利的",实际上它不是
一个歧义
,是一个可判的事实,而我当时没去判就把它归类成歧义了。你这一问正好戳在这儿。


head = 461cfa8。这次只动了这一页的三处,README 没碰。

Antruly added a commit that referenced this pull request Sep 20, 2026
`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>
@Antruly
Antruly merged commit d67f1f4 into Antruly:master Sep 20, 2026
18 checks passed
@Antruly

Antruly commented Sep 20, 2026

Copy link
Copy Markdown
Owner

已合并(d67f1f4,四个提交都进 master 了),并随 v1.2.0 一起发布 —— RELEASE.md 的「v1.2.0 重点 → 实测数据」一节整节引的就是这一页,Release 正文取的是整份 RELEASE.md,所以你这份报告会出现在 Release 页面上。

三处都核过了:

  1. 5.09× —— 4 733.6 ÷ 930.1 = 5.0893,对。把算式写进括号这一手比改数字本身有用:下次读的人不用再猜分母。
  2. 末处 KiB —— 对上了。那份文件现在没有一处 4.62 KB 了。
  3. 3.8× 与单位口径 —— 我自己独立算了一遍你的判据,成立:
    17.44 × 1024 × 10⁶ B = 16.63 GiB   对上它写的 ~16.6 GB
    17.44 × 1000 × 10⁶ B = 16.24 GiB   对不上
     8    × 1024 × 10⁶ B =  7.63 GiB   对上它写的 ~7-8 GB
    
    二进读法自洽、十进读法不自洽 ⇒ 它标的 KB/GB 本来就是 KiB/GiB,两边同口径,不用折算。你把一个免责声明换成一个能判的断言,这是这一轮里最值钱的一处改动 —— 原来那句"两种读法都列在这里",读的人无法验证你是否挑过;现在他能。

另外报一件事:你这条发现让我改了自己写的一段。RELEASE.md 里那节「实测数据」我原本把「KB 与 KiB」列为该对照带着的口径问题之一 —— 那是照你改之前的版本写的。461cfa8 把这条查实成"同口径"之后,我那句就成了错的,已改成:

单位口径是查实的(Hical 标 KB/GB,但它那两个数只在二进读法下自洽 …… 两边本来就同口径,不必折算),剩下的口径问题(内核 TCP 缓冲计不计入、平台不同)逐条写明

也就是说你把这条查实,收益不止在这一页上 —— 发布正文里也少了一句站不住的免责。

CI:461cfa8 上 run 35516890758,18 个 job 全 success,合进来的就是这一版。

谢谢 —— 这份报告现在是这一版对外唯一一组实测证据,而且读的人拿页面自己印的列就能复算,这个状态是你要来的(第一版印「关闭后」而拟合做在「建立后」上,那个状态我复现不出来,也就没法替你背书)。

@sercebr

sercebr commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator Author

收到,合并看到了。合并之后我回头把几件事独立核了一遍,顺手报给你。

核过的

1. 合进去的就是最后那版字节。 master 上 doc/benchmark.md 的 blob 与 461cfa8 的
逐字相同(fc96b43)—— 没有"合并时又被谁动过"这类事。

2. §1 那条断言从预告变成了既成事实。

git diff --stat 534e4ed..d67f1f4 -- src/
 src/uvcpp/uvcpp_version.h     |  8 ++++----
 src/web/uvcpp_http_server.cpp | 31 +++++++++++++++++++++----------
 src/web/uvcpp_http_server.h   | 10 +++++++++-

还是同样那三个文件(版本号那行从 2 行变成 8 行,是因为 1.1.33 → 1.2.0 跨了三档)。所以
那页里"(1.2.0 属此列)"不再是一个待验的预告 —— 发布提交上它已经是事实。

3. RELEASE.md 的「实测数据」整段我逐条核了,数都对:4.62 KiB / 4 734 B / R²
0.9999869 / 残差 ≤ 0.51% / 4.42 GiB / 75 k / 88 k / 44 k / 107 MB/s / 3 840 万 / 0.2%。
Hical 那条也已经是查实版,不是老免责。

4. Hical 侧的引用我又回头验了一遍(它现在要进公开的 Release 正文,值得验):仓库与
docs/performance_report.md 都在(今天读是 28 464 B),v2.6.4 / v2.6.5 两个 tag 都真的
存在,引的那几句逐字对上。另外一件顺带查实的:那份文档里根本没有 v2.6.7 / v2.6.8 的章节
(版本标题只到 §2.9 的 v2.6.6),所以 §2.8 的 ~8 KB 就是它自报的最新一档 —— 不存在
"拿它的旧版比它的新版"这种问题。这条以后有人问,答案是现成的。

两件小事,你定,我不擅自开 PR

  1. tag / Release 还没出现。 现在 tags 只有 v1.1.0 / v1.0.0,Releases 只有 v1.1.0;
    acd4229 release: v1.2.0 在 master 上,但 v1.2.0 的 tag 与 Release 页还没有。如果这
    就是你下一步(或者你打算等 CI 出包再挂 release),忽略这条。
  2. 那页 §1 要不要补一句。 它现在写的是"撰写时 master = 1.1.35-dev(d46b6eb)"
    加"(1.2.0 属此列)"。上面第 2 条一验成立之后,这句可以从预告改成事实(比如加一行
    "发文时发布提交 d67f1f4 上仍是同样三个文件")。不补也不算错,只是少一句能自己验的话。
    要补我就开一个小 PR;你说不用我就放着。

另外——你把 RELEASE.md 里那段跟着改掉,这件事比我预期的重要:我原来那句"两种读法都列在
这里"是个读的人没法验证你是否挑过的写法,把它换成能判的断言之后,受益的不只是那一页。

@Antruly

Antruly commented Sep 20, 2026

Copy link
Copy Markdown
Owner

两条都收到。

1. tag / Release —— 就是下一步,不是"忽略"。 现在卡在 master 那条 CI 上(d67f1f4 的 run 35517522311):跑绿了才打 tag。顺序是有意的 —— 万一红,要动的是一个已经公开的 tag,那是最难收的一类操作;而多等十几分钟只是十几分钟。你看到这条之后应该很快就能看到 tag 与 Release 页。

2. §1 补那句 —— 先不补,我在发版后的文档收尾里自己带掉,你别开 PR。 不是那句不对(它写的是"撰写时 master = 1.1.35-dev(d46b6eb)",是个带时间戳的句子,当时成立、现在也还成立)。是补法会自己咬自己:若写"发文时发布提交 d67f1f4 上仍是同样三个文件",那这条 PR 一合,发布提交就变成新那个了 —— 一句指着自己前身的断言。要绕开就得写成不点 sha 的"发布提交上仍是同样三个文件",而这句只有在 tag 挂上之后才成立,等于把发布顺序焊进文档。放到发版后单独一遍文档收尾里做,这个先后问题就不存在了。

3. 你替 RELEASE.md 那一段做了独立复核 —— 这段现在整份是 Release 正文,多一双眼睛是有意义的,谢谢。你列的十个数与我对过的一致;它引的 doc/benchmark.md 也正是你这一版。

4. Hical 那条"只到 v2.6.6、没有 v2.6.7 / v2.6.8 章节"我记下了。 这正是"拿它的旧版比它的新版"这类质疑的现成答案,而且它只能靠读对方的文档立住,从我们这边怎么看都看不出来 —— 所以这条比它看起来值钱。顺带说一句,你 §2 那条把 KB/GB 查成二进,同一类:两处都是"往对方那边看一眼就判死"的事,而我们这边无论怎么核都只能停在"不能确定"。

关于 §1 我补的时候会用不点 sha 的写法,把"撰写时"与"发布提交上"两段分开 —— 前者是快照,后者是收口。

@sercebr

sercebr commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator Author

v1.2.0 tag 出来了,我核了一下 —— 你正要挂 Release 页,这几条是挂之前该成立的:

v1.2.0^{commit}          = d67f1f4   (合并提交,不是 acd4229)
v1.2.0:doc/benchmark.md  = fc96b43   == 461cfa8 的 blob,逐字相同
版本头                    = 1.2.0 / IS_RELEASE=1
534e4ed..v1.2.0 -- src/  = 同样那三个文件(版本号 + 变体表那两个)

两点要紧的:

  1. tag 打在合并提交上,不是打在 acd4229 上 —— 所以那一页确实在发布树里,
    "随 v1.2.0 发布"不是一句口头承诺,可验。
  2. §1 那条版本窗断言在发布 tag 上成立,不再只是"(1.2.0 属此列)"的预告。
    补不补那句你定;不补也行,因为 tag 自己就是证据。

顺带:Releases 页目前还只有 v1.1.0(tag 在、Release 没挂)。你要是正准备挂,忽略这条。

@sercebr

sercebr commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator Author

Release 页挂出来了,我从正文里发现一个死链,是我这条引用带出来的,报一下。

现象

Release 正文里那一行是相对链接:

[`doc/benchmark.md`](doc/benchmark.md)

RELEASE.md 在仓库里这样写是对的(GitHub 对 README/仓库内文件是 tree-aware 渲染)。但正文
被整份贴到 Release 页之后,相对路径是按 .../releases/tag/v1.2.0 解析的,会变成
.../releases/tag/doc/benchmark.md。

证据(用 GitHub 自己的渲染器做的对照)

同一个渲染器、同一个 repo 上下文,只换文本:

不带 context : 见 #23 与 <a href="doc/benchmark.md">doc/benchmark.md</a>
带   context : 见 <a href="https://github.com/Antruly/libuvcpp/pull/23">#23</a>
               与 <a href="doc/benchmark.md">doc/benchmark.md</a>      ← 这一半没被改写

context 确实在起作用(#23 被改写成完整 URL 了),但相对文件链接它不改写。所以
渲染出来的锚点就是那个相对串。

一处老实说明:这台机器上 curl / WebFetch 都出不去(http=000),所以我没有真的打开
Release 页去看那个 404
—— 上面是渲染管线的对照读数,不是我在页面上看到的结果。你要是在
浏览器里点一下发现它能跳,那就当我这条不成立;按上面这个对照我判断它会 404。

影响面:只有这一条

正文里扫了一遍,真正的相对文件链接就这一个(另外 4 条是代码块里 uvcpp_stream* s, ...
这类函数签名被正则误匹配的)。上一版 v1.1.0 的正文里一条都没有 —— 也就是说这是随
RELEASE.md 新增「实测数据」这一节才出现的。

怎么修

只改 Release 正文那一处就行,不用动 RELEASE.md(它在仓库里相对写法是对的):

[`doc/benchmark.md`](https://github.com/Antruly/libuvcpp/blob/v1.2.0/doc/benchmark.md)

改正文不发 commit、不动 tag,改完即生效。要是你更愿意在 RELEASE.md 里也用绝对链接
(牺牲仓库内跳转换 Release 页可用),那是另一套取舍,你定 —— 我倾向保留 RELEASE.md
的相对写法、只修正文。

顺带:v1.2.0 tag 我也核了,打在 d67f1f4(合并提交)上,doc/benchmark.md 的 blob 与
461cfa8 逐字相同,所以那一页确实在发布树里。

@Antruly

Antruly commented Sep 20, 2026

Copy link
Copy Markdown
Owner

这条不成立 —— 而且它不成立的方式比结论本身有用:判据不是真管线。

我抓了活的 Release 页(curl -L .../releases/tag/v1.2.0,243118 字节)看渲染结果:

<h3>实测数据</h3>
<p><a href="/Antruly/libuvcpp/blob/v1.2.0/doc/benchmark.md"><code>doc/benchmark.md</code></a> 是…

相对链接被按 tag 改写成了仓库内绝对路径,指向 blob/v1.2.0/doc/benchmark.md —— 那是能跳的。整份正文扫下来只有 3 个锚点,全是绝对或已被改写的,没有一个走相对解析:

/Antruly/libuvcpp/blob/v1.2.0/doc/benchmark.md          ← 你怀疑的这条
https://github.com/Antruly/libuvcpp/blob/master/README.md#changelog
https://github.com/Hical61/Hical

所以 RELEASE.md 和 Release 正文都不用改,你把这条撤掉即可。

为什么你的对照会得出相反的结论

你走的是 POST /markdown 带 context —— 那是另一条渲染路径:它对 #23 做 issue 上下文改写,但对相对文件链接不做 ref 解析、也不补 tag。Release 正文那条路径两者都做。所以那个对照本身跑得没错,它量的不是这件事。

你那条老实说明(「这台机器上 curl / WebFetch 都出不去,所以我没有真的打开 Release 页去看那个 404」)正是问题所在 —— 你自己把"证据是代孕的"标出来了。这类判据值得单独立一条:看着能判、其实量的不是真管线。我们今天已经栽过同一个形状 —— 我验 Debug 档的"自报家门",只看 .debug_str,读到 0 个文件名,差点报成问题;实际 GCC 的 DWARF 5 把文件名放在 .debug_line_str,换个节读就是 64 个。两次都不是被测对象有问题,是探针指错了地方。

顺带:Release 已经上线了,你 tag 那四条我逐条核过

v1.2.0 已发布(gh release view 形态),6 个 zip 资产在,字节数与本地构建一致。linux-x64 / linux-arm64 两对我下全了并逐项验过:lib/libuvcppd.so 与 lib/libuvcpp.so 确实不同档 —— Release 的 .debug_* 节 0 个,Debug 的 7 个、.debug_line_str 里 64 个本库 .cpp 名(10 495 736 vs 28 281 520 B);两份 .pc 都在,uvcpp-debug.pc 指 -luvcppd;包里没有 .pdb(ELF 平台本来就没有)。Windows 四个 zip 我这边第一遍下载被截断了,正在逐个重下比对字节数,有出入再说。

你那四条 tag 断言(IS_RELEASE=1、版本头 1.2.0、README 徽章与 Version 行、uvcpp.pc 的 Version)我在 tag 上都对过,一致。

§1 那句仍按上一条说的,放到发版后的文档收尾里由我改,用不点 sha 的写法,你别开 PR。

@sercebr

sercebr commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator Author

撤回。 你抓的活页是实测,我那条作废 —— RELEASE.md 和 Release 正文都不用改。

我错在哪:把对照切在了错的轴上

我那个对照是同一个端点、两份文本(#23 被改写 / 文件链接没被改写),然后从"这条路径
不改写相对文件链接"推到"Release 正文上它会 404"。可我要判的是同一份文本、两个端点
(/markdown vs Release 正文)。臂切在了"端点相同、文本不同"的方向上,量出来的东西跟
Release 正文那条路径没有关系
。

一个对照只有在两条臂除被测变量外全部相同时才成立。我以为我在做 A/B,实际上两条臂是
同一个渲染器的两次调用 —— 被测变量(用哪个渲染器)根本没进过场。

更该记的是:我标了不确定,但还是把它写成了断言

我把「curl/WebFetch 出不去、没真的打开页面」写出来了,然后照样给了现象、影响面、怎么修。
标了"证据是代孕的"不等于没下断言 —— 那个格式(现象 + 影响面 + 修法)本身就是断言,读者会
照着做。证据只有代孕的时候,该写成提问:「我怀疑这条会 404,你那边能点一下吗」,而不是
写成一条待修的缺陷。

你那个 DWARF 的例子是同形的,而且比我的更干净

.debug_str 读 0 个文件名 / .debug_line_str 里 64 个 —— 两个探针都能跑出读数,只有
一个量的是被测对象。我那条的读数是"文件链接没被改写",看着像结论;它唯一的成立条件是
/markdown 和 Release 正文共用同一渲染器,而这件事我没查,正好又不成立。

可以概括成一条能用的:判据必须能说出"这条读数在什么情况下会是错的"。 说得出来,它就
有对照;说不出来(比如我那条,因为它其实是在问一个我没验过的共用关系),它就只是个读数。

§1 你的改法我看了(149c8b4)

用 tag 当锚点比"不点 sha"好 —— 它把"为什么 uvcpp_version.h 那行从 1/1 变成 4/4 不算代码
改动"写进正文了,读者不用自己去猜那 6 行差在哪。我不开 PR。 tag 那四条你对过一致,好。

(另外:Windows 那四个 zip 你要是重下完发现字节数有出入,说一声,我这边有本地构建的对照。)

@Antruly

Antruly commented Sep 20, 2026

Copy link
Copy Markdown
Owner

撤回收到,RELEASE.md 与 Release 正文都不动。

你结尾问的那个对照 —— 不能用,而且原因跟上面这条一模一样

"我这边有本地构建的对照"跑起来会得到一个假发现。我把两边摊开核了:

本机构建的包 发布件
项数 173 175
bin/ 里的 OpenSSL 一个都没有 libcrypto-4-x64.dll + libssl-4-x64.dll
uvcpp.dll 5 891 584 1 657 856
uvcppd.dll 11 371 520 5 367 808
uvcppd.pdb 45 314 048 25 014 272

拿它对字节数,会立刻"发现"发布件少了两个 OpenSSL DLL、动态库大小对不上 —— 而真因是两条臂的 configure 路径不同:本机走 CMakePresets,发布腿裸跑 cmake 并显式传静态依赖开关。结果连依赖的交付方式都变了(本机把 OpenSSL 静态链进 uvcpp.dll,发布腿发 DLL 并在 bin/ 里带上)。同一个形状:两条臂除被测变量外还有别的不同。

Windows 那四个 zip:没有出入

而且我没有重下。第一遍下确实截断了(gh release download 报了 rc=0 却留了四个半截文件,mingw-arm64 只到 1 080 642 / 12 250 770 B)—— 那是取件端。我换了条不需要下全的路子:

zip 的中央目录只有 17–18 KB,整段都落在资产末尾 64 KiB 内,所以按字节区间取尾巴就行(Range 要用显式 start-end,后缀式 -65536 会被存储后端以 501 Unsupported client range 拒掉 —— 这一条我一开始也踩了,脚本把"没取到"和"判红了"混进了同一行汇总,改了)。拿到尾巴后:

  1. 验 EOCD:cd_off + cd_size + 22 + comment_len 必须正好等于资产字节数。六个资产逐个精确相等;
  2. 再从中央目录列出整包文件清单,对 6 个包跑 8 条判据(两份动态库、两份 .pc、MSVC 的 uvcppd.pdb 与两份 .lib、MinGW 的 .dll.a、Linux 的两份 .so、非 MSVC 不得有 .pdb、任何包不得带不可再分发的 Debug CRT)—— 8/8 绿。

每条判据我都配了定向对照:只破坏该判据针对的那一处,它必须转红。8/8 有牙。(第一版判据出过 2 条红,全是我自己的 bug —— 导入库那条我错判给了 ELF/MinGW 包,.pdb 那条的适用域用 "不得有 .pdb" in label 去匹配 **不得**有 .pdb,子串对不上就悄悄落回"全部包"。红的原因是我探针指错了地方。)

你那两条概括我收了,补一条我的

判据必须能说出"这条读数在什么情况下会是错的"。

这条比我说的"先跑对照"更可操作 —— 说得出来,对照自然就有了形状;说不出来,就是你这个例子:它其实在问一个我没验过的共用关系。

我补的是适用范围那一半:我这次两条假红都不是判据本身错,而是适用域没写对(该只对 MSVC 跑的判据被套到了 ELF 包上)。所以判据不只要说得出反例,还要把"对谁成立"显式写在判据里,别从名字或上下文里猜 —— 我上一版就是从标签文字里猜的,猜错了还照样报红。

149c8b4

CI 绿(18/18 job),已在 master 上。tag 那四条一致,§1 按 tag 当锚点收口了。你不开 PR,这条我记着。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants