编程语言研究书库

第 9 章拆解(下)— 异步 TCP 代理与内核多路复用

异步 TCP 代理与内核多路复用

这一半讲什么: 上一篇的积木在这里变成真东西。先是 TCP 代理——同步基线版一次只伺候一个连接, Io.Group 重构版一发不收;配套的压测器自带「长度前缀」私有协议,把同步与异步的墙钟时间差量出来 (220 ms 对 56 ms,4 连接)。然后下到内核:不用 std.Io 的任何抽象,直接拿 C 绑定写 kqueue 和 io_uring 两版单线程回显服务器,看清事件循环背后真正的机器。最后用 echoBench 把数字钉在板上。

主线例子:代理的一次完整会话。客户端经代理连到后端,两个方向的字节流怎么同时泵、 谁先断怎么收场——代理是任何语言 I/O 能力的试金石,这两个问题答不好,别的都免谈1。

1. 为什么偏偏是 TCP 代理

TCP 代理是系统编程的经典练习,书称它是任何语言 I/O 能力的优秀试金石(litmus test):它坐在客户端和目的地服务器之间,双向转发原始字节流2。一个同步代理需要每个连接两个专用 OS 线程(数据流两个方向各一个);异步实现则用 std.Io 把几千对代理会话复用到单个执行线程上3。

异步代理的优势是水平扩展性:不给每个并发连接分配 2 MB 或 8 MB 的栈,内存占用低到只剩应用缓冲本身;再借 io_uring/kqueue 把「系统调用税」压下去——不是不停在用户态/内核态之间切换去查套接字有没有数据,而是批量提交,内核只在有可行动数据时才通知你。吞吐显著更高、抖动更低,尤其在 thread-per-connection 模型会被调度延迟卡死的高并发负载下4。

但书立刻把代价摆上桌面,这三条是全章的诚实基线:

  • 架构复杂度上升:同步程序里,调用栈隐式替连接记账;异步系统里,开发者必须显式管理状态机——读了什么、还有什么在等写。「状态记账」让代码更难调试,执行流不再是线性的5。
  • 事件循环饿死的风险:单个请求里的重计算(复杂的包检查、加密)会堵住整个事件循环,拖死所有其他活跃连接。Zig 没有隐藏运行时救你,让循环保持转动的责任全在函数设计上6。
  • 上面的 220 ms 对 56 ms 只在 localhost 上量——回环延迟约等于零,payload 小(4096 字节),后端 trivial 地快。真实部署里网络延迟、更大 payload、更慢后端会让差距拉得更大;这个基准抓住的是差距的形状,不是差距的完整量级7。

2. 同步基线版:两个精心设计的细节

2.1 transferOneWay:共享的搬砖函数

同步版与异步版共用的底层劳力是 transferOneWay(io, src, dst):从源泵数据到目的地,直到源耗尽或连接断掉8。它的输出实录值得先瞄一眼——echo "hello from the proxy" | nc 127.0.0.1 8080 原样弹回,整个环路工作正常9。

函数里有两个刻意为之的决定。第一,零长度内部缓冲的读器:

var r = src.reader(io, &[0]u8{});

图说:Reader 的内部缓冲非零时,readVec 会把它和你的 data 缓冲一起排进 iovec 数组——溢出的字节可能溅进内部缓冲,随后的调用为了填满剩余空间而阻塞;万一发送方真的没东西可写了,就是死锁。零长度内部缓冲让 readVec 直接读进 data、操作系统一交付任何字节就返回——真正的短读(true short read),转发代理的命脉10。每块数据立即写向目的地并 flush,数据零延迟到达对端;循环退出时 dst.shutdown(io, .send) 做 TCP 半关闭,告诉对端「这边说完了」11。循环内的错误被静默丢弃——连接断开在这里是常态而非异常;生产代码至少该用 @errorName(err) 记日志12。

2.2 handleSession:双向并发与半关闭的协奏曲

一次代理会话要管两个方向——单方向函数得跑两份。同步基线的 handleSession 连上远端、起两个异步任务(客户端→远端 task_c2r、远端→客户端 task_r2c)、顺序 await 两个13。这里的协奏关系是全章最精妙的一段:

c2r 读到 EOF → 对 remote 的写侧 shutdown(.send)
           → 后端知道请求完毕,发响应、关闭
           → 这解锁了 r2c:r2c 把剩余数据转回客户端,也结束
两个任务都完成 → handleSession 返回 → defer 关掉两条流

图说:会话保持足够久,让完整的「请求-响应」交换完成,不管哪边先开口14。同步版 main 的 accept 循环老问题:一个会话活着,accept 就闲着——这个局限正是下节重构的靶子15。后端的极简替身:socat TCP-LISTEN:9000,fork EXEC:/bin/cat,一条命令立起回显后端16。

3. 异步重构:三处结构性变化

proxyAsync.zig 与同步版共享 transferOneWay,变化集中在三处17:

变化一:handleClient 返回 void 而非 !void。 因为它要被 Io.Group 以「一发不收」(fire-and-forget)方式发射——没有调用者在等一个错误,所以错误全部就地处理:catch return、catch {};连不上后端就打印一行诊断、早退,不再沿错误联合向上展开18。

变化二:acceptLoop 用 Io.Group 当并发任务池。 每接受一个客户端,group.async(io, handleClient, .{...}) 把它作为一发不收的并发任务发射、立即返回——accept 循环不等会话完成就去迎接下一个连接。Io.Group 跟踪所有在飞的会话任务,defer group.cancel(io) 保证 acceptLoop 万一退出时全部取消。这就是与同步版的结构性分水岭:许多会话可以同时活跃19。

变化三:main 把 acceptLoop 自己包进 io.async。 因为 acceptLoop 永远跑下去,包成 Future 给了 main 一个干净的取消把柄(defer 里的 acceptor.cancel);随后的 acceptor.await 把 main 挡在服务器的整个生命周期里,直到外部信号或错误把它带倒20。参数解析也缩短了:解析失败静默回退默认值,不再返回错误21。

4. proxyBench:长度前缀协议与第一组对照数字

手动一个个开连接永远暴露不了同步代理的串行瓶颈——人类没法从终端同时发 50 个 TCP 连接。所以需要专用压测器;它还必须绕开 TCP 半关闭(基准协议里半关闭语义太别扭),答案是长度前缀协议:

客户端: [4 字节大端整数 = payload 长度][恰好这么多个字节]
后端:   读长度头 → 读足那么多字节 → 原样写回
客户端: 读回同样多的字节 → 记耗时

图说:双方对传输尺寸有共识,谁都不用信号报流尾——交换在双向固定字节数后确定性地完成22。proxyBench.zig 双模式:--server 9000 --latency 50 是带 50 毫秒模拟延迟的回显后端;--bench 127.0.0.1 8080 --conns 4 --bytes 4096 是基准客户端23。

第一组对照数字(4 并发连接 × 4096 字节,后端延迟 50 ms):

代理墙钟吞吐平均连接延迟
tcpProxy(同步)220 ms0.07 MB/s136.74 ms
proxyAsync(异步)56 ms0.28 MB/s55.71 ms

图说:墙钟快约 75%,单连接延迟低约 59%24。原因一句话:同步版把连接串行化——accept 循环被每个会话的时长阻塞,4 个连接一个接一个,136 ms × 4 ≈ 546 ms 的账被代理流水压缩成 220 ms;异步版把每个会话派给独立的并发任务,4 个连接同时在飞,墙钟逼近单个连接的耗时25。两组都 4 连接零失败——正确性没有为架构让路26。

5. std.Io 的盖子底下:四种后端

本章一直在用 std.Io 的功能,它把平台专用的 I/O 执行藏在统一接口后面。默认后端 std.Io.Threaded 用的是真实 OS 线程池(默认比逻辑 CPU 数少一个)27。要换事件驱动,得显式构造事件化后端:Linux 用 std.Io.Uring,macOS 用 std.Io.Dispatch(底层是 Grand Central Dispatch),BSD 用 std.Io.Kqueue28。

老接口为什么不够:select(2) 和 poll(2) 每次调用都要内核扫描所有被监视的文件描述符——O(n),服务器盯着几千个连接时痛不欲生。io_uring(Linux)和 kqueue(macOS/BSD)就是为回答这个问题而生的:前者是共享内存环,内核和用户空间不用每条操作一次系统调用就能交换成批的 I/O 操作;后者是事件通知队列,只交付真正就绪的描述符,不要应用扫全表29。

接下来两个程序把抽象整个掀开:同一个 TCP 回显服务器,分别对着两个内核接口裸写,std.Io 完全不插手——先看清机器的原貌,再谈抽象值不值30。

为什么非要新接口?把老接口的账摆出来就清楚了。select(2) 和 poll(2) 每次调用都要内核扫描所有被监视的文件描述符——O(n);服务器同时盯着几千个连接时,每个事件都要为「绝大多数根本没就绪的描述符」付一遍扫描钱31。kqueue 的答案是事件通知队列:内核只交付真正就绪的描述符,应用不再扫全表32;io_uring 的答案是共享内存环:连「就绪通知」本身的系统调用都被批量化,请求和结果在用户空间与内核之间直接换33。两个都是单线程的并发解法:一个线程、无锁、不在连接处理器之间切换上下文,服务器却对每个客户端保持完全响应34。一个口径预告:macOS 的 std.Io 内部用的是 GCD 而不是 kqueue 系统调用,下面的 kqueue 程序是「内核接口的窗口」,不是「Zig 标准库在 macOS 底下干什么」的指南35。

6. kqueue 版回显服务器:窗口式机器

两个裸写程序共享同一副骨架:@cImport 建绑定套接字;fcntl 设非阻塞;无限事件循环里按事件类型分发;按文件描述符值索引的堆上 per-connection 缓冲数组(上限 MAX_FD = 1024);监听套接字设 SO_REUSEADDR(服务器重启不必等操作系统放端口);comptime 守卫(@compileError)保证编错平台直接构建失败36。

kqueue 版的问答方式:一个 kqueue() 描述符 + kevent 注册兴趣(EV_ADD);事件循环第二次调 kevent、空 changelist,阻塞到事件到达,每次最多收 MAX_EVENTS = 64 条通知;每条事件的 ident 字段携带肇事 fd。循环里两岔:ident 是监听套接字 → accept 拿新客户端 fd 并用 EV_ADD 注册;是客户端套接字 → c.read() 读后立刻 c.write() 回写。对端断开靠事件上的 EV_EOF 标志发现。读写是回调内直接同步的:数据到了、被弹回、循环回到等待37。

把两个裸写程序并排,同与异一张表收齐38:

kqueue 版io_uring 版
问答方式内核推「谁就绪了」应用推「要干什么」、内核回「干完了」
读写发生在哪里事件回调内直接 read/write内核代劳,应用只看完成通知
操作间状态无(user_data 不需要)位打包进 64 位 user_data
批量粒度每批一次 kevent + per 连接 read/write多条 SQE 一次 io_uring_enter
断连发现事件上的 EV_EOF 标志RECV/SEND 完成结果的错误码

图说:kqueue 是「就绪通知」的模型,io_uring 是「提交-完成」的模型——前者问内核「谁能动」,后者告诉内核「去干这些,干完喊我」39。同一副骨架(非阻塞、按 fd 索引的缓冲、SO_REUSEADDR、平台守卫)套两种哲学,并排读完,事件化后端的抽象为什么值得有,就不再是信仰而是账目40。

7. io_uring 版:共享内存环与续体式控制流

io_uring 版走 std.os.linux.IoUring 包装。核心机制是一对共享内存环:应用把「要执行的操作」写成提交队列项(SQE,Submission Queue Entries)放进提交环;内核完成后把结果写成完成队列项(CQE,Completion Queue Entries)放进完成环——每条操作一次系统调用的模型被整个换掉41。

控制流因此换了物种。程序把操作类型(accept / recv / send)和文件描述符位打包进每个 SQE 的 64 位 user_data 字段——高 32 位是操作类型,低 32 位是 fd;完成处理函数解这个字段,知道「刚完成的是什么、接下来该排队什么」42。于是流程变成续体式的而不是 kqueue 那种「到了就读写」的直给风格:

recv 完成 → 排队一个 send
send 完成 → 排队下一个 recv
accept 完成 → 给新客户端排队 recv,并立刻再排队一个 accept
              (下一条连接不需要额外一次系统调用往返)

图说:io_uring 能把多条操作批量进单次 io_uring_enter 系统调用,连接数高时显著削减跨内核的开销;kqueue 是每批事件一次 kevent 调用,然后 per 连接各一次 read/write43。简单回显服务器上差距温和;负载上去了、并发连接多了,就可测量了——echoBench 就是为量化它造的44。

8. echoBench 与第二组数字:各平台的天花板

echoBench 是并发负载发生器:每轮一个 Io.Group、每连接一个 group.async 协程——所有连接作为事件循环上的协程并发建立与操练,而不是顺序一个个来;顺序基准永远喂不饱单线程事件驱动服务器45。每连接任务从 connect 前打时间戳到收完最后一字节,四个共享原子计数器(done / failed / bytes / latency_ns)无锁累计46。

测试条件:200 并发连接 × 8192 字节 × 5 轮。macOS(M4 Pro,kqueue)实录:第一轮 89.87 MB/s、平均延迟 0.73 ms,第二轮跳到 106.78,第三轮起稳定在 120–126 MB/s、延迟跌到 0.08–0.09 ms、墙钟 12–13 ms47。Linux(Intel i7,io_uring)实录:第一轮 12.15 MB/s,爬到约 20 MB/s 稳定,延迟 1.4–1.9 ms,墙钟 77–82 ms48。两边 200 连接 × 5 轮全部零失败。

书在这里踩了一脚急刹车,这段诚实声明是全书方法论的高光:

这不是一场公平或直接的对比。kqueue 的数字在 Apple M4 Pro 上量,io_uring 在 Intel i7 上量——两台内存带宽、回环栈性能、CPU 特性完全不同的机器。从中拿走「kqueue 比 io_uring 快六倍」的结论,是数据撑不起的。 数字真正展示的是每个接口在自家平台上的天花板。有意义的对比需要同机双跑,而 kqueue 是 macOS/BSD 原语、io_uring 是 Linux 专属,这本身就不直接可行。49

Linux 侧的五轮曲线同样先低后平,完整数字50:

轮次吞吐平均延迟墙钟
112.15 MB/s1.96 ms128 ms
218.99 MB/s1.85 ms82 ms
319.93 MB/s1.62 ms78 ms
419.45 MB/s1.36 ms80 ms
520.21 MB/s1.53 ms77 ms

图说:首轮偏低是预热效应——页缓存、CPU 分支预测、连接处理代码全部进入热态51。而且这次基准量的主要是硬件不是内核接口:M4 Pro 内存带宽和回环栈太强,i7 那代架构追不上;io_uring 的批量优势(高连接数下系统调用更少)在回环 I/O 被内存带宽主导时显现不出来。同硬件上 io_uring 预期会在更高连接数反超 kqueue52。所以这组数字的正确读法是「这就是每台服务器在各自硬件上达到的水平」,而不是「kqueue 比 io_uring 快」53。

9. 近景深潜:四个机制拆开看

9.1 压测器为什么要自己造:零依赖测试环境

proxyBench 的设计本身是个方法论教材:后端回显 + 基准客户端同体,整个测试环境在一个代码库里跑,没有任何外部依赖54。后端被刻意保持琐碎(读长度、读数据、弹回),确保它永远不是瓶颈——量的是代理,不是后端55。50 毫秒的模拟延迟是它的旋钮:--latency 50 让每个回显都慢半拍,把「等待」从噪声放大成信号——没有这 50 ms,4 个连接的串行与并发在墙钟上几乎看不出差别56。

客户端侧的数字是这样炼出来的:每条 benchConn() 任务在 connect 之前打第一个时间戳、收完最后一字节打第二个;中间任何一步失败(拒连、短写、短读)只给 failed 计数器加一后立刻返回,不碰其他计数器——所以报出来的平均值只反映成功连接57。时间戳包住的是「connect 到最后一字节」的全程,报出的平均延迟是客户端可见的往返时间,不是服务端处理时间58。group.await 返回后,runRound 用总字节 ÷ 墙钟得吞吐、累计纳秒 ÷ 成功数得平均延迟——一行一个指标,轮与轮、服务器与服务器直接可比59。

9.2 同步版 220 ms 的逐格时间账

主线例子补全。后端延迟 50 ms 设定下,一次经代理的往返:客户端发 4096 字节 → 代理 c2r 转发(→ 后端延迟 50 ms)→ 后端弹回 → 代理 r2c 转发(→ 再 50 ms)→ 客户端收齐,单连接端到端约 136.74 ms(含建连与协议头开销)60。同步代理的 accept 循环把这个数串行累加:

t=0      连接 1 建立,会话开始
t≈137   连接 1 完成(50ms 去 + 50ms 回 + 开销)——期间连接 2/3/4 在 listen 队列里干等
t≈274   连接 2 完成
t≈411   连接 3 完成
t≈548   连接 4 完成(实测压缩到 220 ms,因为部分流水交叠)

异步版:4 条会话同时在飞,墙钟 ≈ 单条 = 56 ms

图说:这就是「NOTE: wall-clock time scales with --conns on tcpProxy (serial), but stays roughly constant on proxyAsync (concurrent)」的出处——同步曲线随连接数线性爬坡,异步曲线基本持平,两条曲线的形状之差就是这一章要卖的东西61。

9.3 kqueue 事件循环的逐步解剖

kqueue 版服务器的完整一轮长这样(全部来自原书描述)62:

第 1 步  kq = kqueue()                     ← 一个队列描述符管全部兴趣
第 2 步  kevent(kq, [监听fd + EV_ADD], …)  ← 注册:告诉内核对什么感兴趣
第 3 步  kevent(kq, null, 0, &events, 64) ← 阻塞;一次最多收 64 条就绪通知
第 4 步  逐条看 events[i].ident:
    ident == 监听fd → accept() 得新客户端 fd → EV_ADD 注册它 → 回第 3 步
    ident == 客户端fd → c.read() → c.write() 弹回 → 若事件带 EV_EOF → 关连接
第 5 步  回到第 3 步

图说:与第 5 章 zwatch 的 kqueue 用法同宗,差别在 flags——那里用 EVFILT_VNODE 盯 vnode 变化,这里盯的是套接字的可读事件;EV_EOF 是 TCP 对端 FIN 到达的内核翻译63。SO_REUSEADDR 的角色不变:监听套接字设上它,服务器崩了重启不必等操作系统慢慢放掉那个端口64。

9.4 io_uring 完成环的解码循环

io_uring 版的控制循环是「完成驱动」的,骨架八行65:

第 1 步  往提交环写 SQE:opcode=ACCEPT(带监听fd)→ io_uring_enter 提交
第 2 步  阻塞收 CQE
第 3 步  解 user_data:高 32 位 = 操作类型,低 32 位 = fd
          ACCEPT 完成 → 给新 fd 排队 RECV;并立刻再排队一个 ACCEPT
第 4 步  RECV 完成(读到 n 字节)→ 给同 fd 排队 SEND(带这 n 字节)
第 5 步  SEND 完成 → 给同 fd 排队下一个 RECV
第 6 步  对端断开 → RECV/SEND 报错 → 关 fd
第 7 步  回第 2 步

图说:accept 完成时顺手把下一个 accept 也排队,新连接到来不需要额外一次系统调用往返——这个「预排队」是 io_uring 版与 kqueue 版在系统调用计数上的结构性差异来源66。而 user_data 位打包是全程序唯一的状态:没有它,完成环里的 CQE 只是一堆「某个操作在某个 fd 上完成了」,程序无从知道该续哪一段67。

9.5 预热曲线:数字该怎么读

macOS 那五轮的完整曲线值得抄下来当基准阅读范本68:

轮次吞吐平均延迟墙钟
189.87 MB/s0.73 ms17 ms
2106.78 MB/s0.46 ms14 ms
3126.32 MB/s0.09 ms12 ms
4123.77 MB/s0.08 ms12 ms
5120.16 MB/s0.09 ms13 ms

图说:第一轮比稳定态慢约 30%,然后陡升、平台化。预热是页缓存、CPU 分支预测、连接处理代码同时进入热态的叠加69。教训有两条:其一,基准报告该注明从第几轮起算数;其二,单次运行的一个数字什么都说明不了——预热没结束就采样,采到的是冷启动不是能力。书的练习里专门有一条:给 proxyBench 加 --rounds N 重复 N 轮、报最小/最大/平均墙钟,把轮间方差量化出来70。

9.6 从练习倒推的生产缺口

章末练习清单本身就是一份「这还不是生产代码」的自白,四条全部指向真实代理的必修课:

练习暴露的缺口
给会话加超时(Io.Select 三个变体:两个方向 + 一个睡眠定时)71挂死的客户端能永久占住一条会话——代理没有空闲断连
给 proxyAsync 加活跃连接原子计数器72可观测性:现在服务器对自己同时伺候多少连接一无所知
proxyBench 加 --rounds N 与方差统计70单轮数字不含方差,性能结论的置信度无从谈起
固定 --conns 50 扫 --bytes 512/4096/16384/6553673只测了一种 payload 形状;大包下缓冲区与批量策略的行为没有数据

图说:书没有假装这个代理能直接部署——它示范的是架构形状,缺口都明码标在练习里74。

9.7 sleepSelect:六行代码看懂 Io.Select 的全部契约

sel.await() 之前的一切都可以浓缩成一个最小例子(书上的 sleepSelect.zig):定义一个「每竞争者一个变体」的联合类型(union(enum){ t1: void, t2: void }),用一块够装全部结果的栈缓冲初始化选择器,sel.async(.t1, 函数, .{参数}) 逐个发射竞争者,sel.await() 阻塞到第一个完成、返回带标签的联合值,switch 按变体分发——defer sel.cancelDiscard() 保证输家被取消、结果被丢弃75。1 秒对 2 秒两个睡眠赛跑,输出永远是 Task 1 finished first76。书点破它的三个典型战场:超时、后端竞速、一切「最快响应说了算」的场景77。注意示例里 sleepFor 吞掉错误只为聚焦机制——生产代码应返回 !void 传播错误,注释里自己写明了78。

9.8 从练习倒推的第二组生产缺口

练习清单的后半段补上前半段没说的两个:其一,给 proxyAsync 加活跃连接的原子计数器(std.atomic.Value(u32),handleClient 开头加一、defer 里减一),并用 proxyBench --conns 20 验证「任何会话关闭前计数先冲到 20」——可观测性从零到一79。其二,给会话加空闲超时:Io.Select 从两个变体扩成三个(两个方向 + 一个睡眠定时),sel.await() 返回 .timeout 就打消息收会话;验证姿势很具体——nc 连上后发呆,看超时是否如期触发80。加上 --rounds N 的方差统计与固定并发扫 payload 尺寸,缺口清单齐了:超时、计数、方差、尺寸扫描——全是「这还不是生产代码」的明码标价81。

10. 长度前缀协议的字节级对账与 io_uring 的一次往返

10.1 长度前缀:四个字节换掉一个半关闭

为什么 proxyBench 要发明协议而不是直接用 echo 惯例(发完 shutdown)?因为基准要的是可重复的确定性:半关闭把「什么时候算完」交给对端行为,长度前缀把答案写死在头四个字节里82。一次 4096 字节往返的字节流长这样(大端长度头,数值为协议演示):

客户端 → 代理 → 后端:  [00 00 10 00][4096 字节 payload]
                        ↑ 4 字节大端 = 4096
后端 → 代理 → 客户端:  [4096 字节 payload](无头,原样弹回)

图说:后端读头、读足、弹回;客户端发完读回同样多字节、掐表。两边对「何时算完」零歧义,交换在双向固定字节数后确定性地完成83。echoBench 测裸回显服务器时连头都省了——raw echo 只镜像到达的字节,benchConn 发多少、读回多少,靠 payload 尺寸本身对账84。

10.2 一次回环往返在 io_uring 版服务器里的完整事件序列

主线例子二号。一个客户端经回环连上 io_uring 版回显服务器,从内核视角看全部事件(事件序列为机制演示,user_data 的具体数值是为演示编的,位打包格式本身照原书)85:

t0  提交环:SQE#1 = ACCEPT(监听fd, user_data = [ACCEPT | 监听fd])
    io_uring_enter —— 一次系统调用,把队列推进内核
t1  CQE#1 到达:user_data 解出 ACCEPT → 得到新客户端 fd=7
    立刻排队两个 SQE:
      SQE#2 = RECV(fd=7, 4KB 缓冲, user_data = [RECV | 7])
      SQE#3 = ACCEPT(监听fd, …)              ← 预排队下一个 accept
t2  客户端发来 4096 字节,CQE#2:[RECV | 7] 完成,n=4096
    排队 SQE#4 = SEND(fd=7, 那 4096 字节, user_data = [SEND | 7])
t3  CQE#3 到达:[SEND | 7] 完成
    排队 SQE#5 = RECV(fd=7, …)                ← 等下一条
t4  客户端收齐 4096 字节、关连 → RECV 返回 0/错误
    关 fd=7,缓冲归还池

图说:整条会话里应用程序侧的系统调用只有「往环里写 SQE」和「io_uring_enter」两种动作,recv/send 本身不占系统调用;kqueue 版同样的事件流则要有「一次 kevent + 每连接一次 read + 每连接一次 write」的对应序列86。连接少时两种税都看不见;200 连接并发时,乘出来的差距就是 echoBench 想量、却被硬件带宽盖过的那部分87。

10.3 echoBench 的内部构造:四个原子计数器与三个指标

echoBench 的测量精度全在一个「每连接任务 + 全局原子账」的结构里88:benchConn 从 connect 前掐表到收齐最后一字节,成功就把 done、bytes、latency_ns 各加一份;任何失败只动 failed、不碰其余三个——所以打印出的平均延迟分母里没有失败者,指标不会被拒连拉低89。group.await 收齐后,三个指标一行一个轮次:吞吐 = 总字节 ÷ 墙钟;平均延迟 = 累计纳秒 ÷ 成功数;墙钟单独成列90。

这个结构同时解释了为什么基准必须是协程并发而不是顺序循环:200 条 group.async 协程在同一事件循环上同时操练服务器,顺序基准永远喂不饱单线程事件驱动服务器——你量到的将是客户端自己的串行,不是服务器的多路复用91。

11. 四种后端一张表:同一个 std.Io,四种底下的机器

把本章的 I/O 执行层摆在一起,全貌是这样92:

后端平台底下的机器你什么时候见到它
std.Io.Threaded全平台真实 OS 线程池,默认比逻辑 CPU 数少一个默认;不构造事件化后端就是它
std.Io.UringLinuxio_uring 共享内存环显式构造;高并发、追求批量
std.Io.DispatchmacOSGrand Central Dispatch显式构造;注意 macOS 的 std.Io 内部本来就用 GCD 而不是 kqueue
std.Io.KqueueBSDkqueue 事件通知队列显式构造

图说:同一个 io.async/await/Io.Group 写法,跑在四种底下的机器上93。这正是第 9 篇「蓝图不是样板」的工程版:接口是稳定的,执行是可换的——业务逻辑和硬件之间被 std.Io 解耦,换平台换的是后端构造,不是代码形状94。但有一条语义差异必须记牢(第 8 篇的注释警告过):Threaded 后端下,从不挂起的纯 CPU 函数会一路跑到完、占着一个工作线程;事件化后端下,这样的函数会堵住整个事件循环——同一个函数,两种后端,两种事故95。

12. 回看前章:这一半是旧零件的新装配

把本章的零件和第 5–8 章摆在一起,会发现没有一件是新造的:

  • kqueue 事件循环与第 5 章 zwatch 同宗:一个 kqueue() 描述符、kevent 注册、阻塞等事件、按 ident 分发。差别只在过滤器——zwatch 盯 EVFILT_VNODE(文件系统变化),本章盯套接字可读;连「每 fd 一个堆上缓冲数组」都是第 5 章 fd 保活问题的回响96。
  • Io.Group 一发不收是第 8 章线程池替代品的网络版:group.async 投任务、accept 循环立即回岗,与 zlocate 的 Scatter 阶段「撒出去就不等」是同一个动作97。
  • 半关闭从第 6 章 echo 客户端一路用到代理:c2r 的 EOF 触发对远端的 shutdown(.send),与 echo 客户端「发完就 shutdown 等回声」一模一样,只是这次它是会话拆收场的扳机98。
  • 零长度内部缓冲的 reader 是第 4 章缓冲经济学的反面教材运用:缓冲区不是越大越好——转发场景里,多一层内部缓冲就多一个「为填满而阻塞」的死锁坑99。

图说:章末那句话在此落地——「系统编程不是一堆不相干的原语,而是一套懂了每个件就能预测地组合的工具」;代理不是新概念,是旧概念在双向字节流上的合成100。

13. 收束:蓝图,不是样板

章末的叮嘱值得原文转述精神:在 Zig 这样快速演化的语言里,把例子当架构蓝图而不是静态样板。标准库函数的具体语法会变,底下的系统原理不变——事件循环怎么复用 I/O、任务怎么挂起防止阻塞、std.Io 这样的接口怎么把业务逻辑和硬件解耦。掌握了「为什么会话要这么分配、poll 列表为什么要手工清理」,任何未来版本的编译器、任何新内核原语到来时你都能改造代码。系统编程的精通不在能编译某段代码,而在能推理数据如何流过内核、流进应用的内存101。

14. 作者的判断与证据

  • 判断: TCP 代理是 I/O 能力的试金石;异步版赢在水平扩展,代价是状态记账与饿死风险26。证据:4 连接基准 220 ms 对 56 ms;正确性两边都零失败。
  • 判断: localhost 基准抓住的是差距的形状不是量级——真实网络下差距只会更大7。这是推断,方向几乎必然成立,但没有实测。
  • 判断(方法论高光): kqueue 对 io_uring 的数字不能跨机比较,读作「各自平台的天花板」4953。证据:两台测试机的硬件差异 + 预热曲线的完整披露。这是全书对基准数据最克制的一段。
  • 判断: 同硬件下 io_uring 预期在高连接数反超,因为批量提交削减系统调用52。证据:机制推演(SQE/CQE 批量 vs per 连接 read/write),本轮基准里带宽主导、优势未显现。

15. 边界与局限

  • proxyBench 只到 4 连接;练习要求补 10/50/100/200 的扩展曲线102。
  • 两个代理的错误处理都刻意简化:后端失败、写错误、断连全部静默;生产代理要分类记日志、瞬时故障重试、上报统计12。
  • 裸写 io_uring/kqueue 版缓冲数组按 fd 索引、上限 1024,fd 复用后越界的防护没有展开;SO_REUSEADDR 只解决重启等端口,不解决 TIME_WAIT 堆积。
  • kqueue 版读写内联在事件回调里,没处理「写没写完」的部分写;io_uring 版同样按完整 send 假设。
  • 事件化后端(Dispatch/Uring/Kqueue)上,从不挂起的纯 CPU 协程会堵事件循环——第 8 篇注释里警告过,本章没有给检测手段。
  • proxyBench 与 echoBench 的客户端都用 page_allocator 分配读回缓冲(见 benchConn 的 page_allocator.alloc),大 payload 下每连接一次堆申请,没有复用池。
  • 裸写两版服务器都只回显、不支持半关闭语义转译;代理场景的 shutdown 传播在事件化模型里需要额外的状态位,本章没有展开。
  • MAX_FD = 1024 的按 fd 索引缓冲数组在 fd 超过 1024(高连接 + 监听 + 日志文件)时会顶穿,生产形态要换动态表。

16. 可带走的

  1. 代理试金石:双向同时泵 + 谁先断怎么收场,两个问题答好才算 I/O 出师。
  2. 零长度内部缓冲的 reader:转发场景的死锁保险——溢出字节进内部缓冲再阻塞等填满,就是死锁。
  3. 半关闭的协奏:c2r EOF → shutdown 远端写侧 → 后端发响应关连 → 解锁 r2c → 双任务齐 → 会话收。
  4. 一发不收任务返回 void:没有错误接收者,错误就地 catch。
  5. acceptLoop 永远跑 → 包进 io.async 拿取消把柄,main 用 await 把自己挡在进程一生里。
  6. 压测协议自己定:长度前缀免半关闭,「多少字节算完」双向确定,零歧义。
  7. 4 连接的账:串行 220 ms ≈ 4 × 单连接;并发 56 ms ≈ 1 × 单连接——形状就是墙钟随连接数线性 vs 持平。
  8. localhost 基准量形状不量量级;读别人的基准数字,先问「两台机器一样吗」。
  9. 首轮永远慢:页缓存、分支预测、代码热态——基准要预热轮,数字从第三轮起才算数。
  10. io_uring 的杀手锏是批量(SQE/CQE 环 + 单 enter 多操作),kqueue 是就绪通知;带宽主导的回环测试里,系统调用优势显不出来。
  11. 平台守卫 @compileError + SO_REUSEADDR + 按 fd 索引的 per-connection 缓冲——裸写内核接口的三件套。
  12. select/poll 的老账:每次调用扫全部 fd;连接上千时,事件通知(kqueue)与批量提交(io_uring)是两种正交的解法。
  13. 同一个 std.Io 接口底下四种机器:Threaded(默认线程池)/ Uring / Dispatch / Kqueue——换平台换构造,不换代码形状。
  14. 基准必先预热:页缓存、分支预测、代码热态让第一轮慢两三成;报告数字从稳定轮起算,单次单轮的一个数什么也说明不了。
  15. 纯 CPU 的协程是事件循环的独占炸弹:Threaded 后端占一个工作线程,事件化后端堵全部连接——重活要么切碎要么挪线程。

17. 原文地图

主题原书章原文位置
代理=试金石Being interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:125(搜「litmus test」)
水平扩展性优势Being interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:143(搜「horizontal scalability」)
状态记账代价Being interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:175(搜「state bookkeeping」)
事件循环饿死Being interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:177(搜「starvation」)
零长度缓冲防死锁Being interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:256(搜「deadlock」)
半关闭信号Being interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:278(搜「half-close」)
同步 accept 闲置Being interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:543(搜「sits idle」)
handleClient 返回 voidBeing interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:619(搜「must return void」)
Io.Group 一发不收Being interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:738(搜「fire-and-forget」)
main 包 acceptLoopBeing interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:853(搜「handle」)
长度前缀协议Being interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:909(搜「four-byte big-endian」)
220 ms 对 56 msBeing interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:955(搜「220 ms」) · :985(搜「56 ms」)
localhost 只量形状Being interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:1060(搜「shape」)
默认后端 ThreadedBeing interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:1072(搜「OS thread pool」)
select/poll 的 O(n)Being interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:1104(搜「scan every」)
macOS std.Io 用 GCDBeing interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:1122(搜「Grand Central Dispatch」)
骨架四件套Being interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:1208(搜「fcntl」)
select/poll 的 O(n)Being interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:1104(搜「scan every」)
两版同为单线程解法Being interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:1188(搜「no locks」)
骨架四件套之 per-fd 缓冲Being interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:1212(搜「per-connection buffers」)
user_data 位打包Being interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:1350(搜「user_data」)
预排队下一个 acceptBeing interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:1388(搜「extra syscall round-trip」)
Linux 五轮曲线Being interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:1678(搜「12.15」)
练习清单=生产缺口Being interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:1864(搜「per-session timeout」)
kqueue 问答方式Being interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:1260(搜「EV_ADD」)
SQE/CQE 双环Being interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:1326(搜「shared memory rings」)
user_data 位打包Being interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:1352(搜「bit-packing」)
续体式控制流Being interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:1356(搜「continuation-based」)
批量削减系统调用Being interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:1394(搜「batch」)
协程并发基准Being interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:1462(搜「sequential benchmark」)
kqueue 五轮实录Being interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:1658(搜「89.87」)
io_uring 五轮实录Being interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:1678(搜「12.15」)
不能跨机比Being interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:1620(搜「cannot support」)
预热效应Being interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:1716(搜「warm-up」)
量的主要是硬件Being interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:1734(搜「hardware」)
同硬件 io_uring 预期反超Being interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:1754(搜「pull ahead」)
蓝图不是样板Being interested in the first result onlytext/109-fm-being-interested-in-the-first-result-only.txt:1776(搜「architectural blueprints」)

本篇拆解依据原书第 9 章后半。每条出处可用 node scripts/book-verify.mjs systems-programming-with-zig 回核。

Footnotes

  1. 出处:「Being interested in the first result only」第 125 段(text/109-fm-being-interested-in-the-first-result-only.txt:125,搜「litmus test」)。 ↩

  2. 出处:「Being interested in the first result only」第 127 段(text/109-fm-being-interested-in-the-first-result-only.txt:127,搜「forwarding raw byte streams」)。 ↩ ↩2

  3. 出处:「Being interested in the first result only」第 133 段(text/109-fm-being-interested-in-the-first-result-only.txt:133,搜「multiplex」)。 ↩

  4. 出处:「Being interested in the first result only」第 143 段(text/109-fm-being-interested-in-the-first-result-only.txt:143,搜「horizontal scalability」)与第 159 段(text/109-fm-being-interested-in-the-first-result-only.txt:159,搜「system call tax」)。 ↩

  5. 出处:「Being interested in the first result only」第 175 段(text/109-fm-being-interested-in-the-first-result-only.txt:175,搜「state bookkeeping」)。 ↩

  6. 出处:「Being interested in the first result only」第 177 段(text/109-fm-being-interested-in-the-first-result-only.txt:177,搜「starvation」)与第 181 段(text/109-fm-being-interested-in-the-first-result-only.txt:181,搜「rests entirely」)。 ↩ ↩2

  7. 出处:「Being interested in the first result only」第 1048 段(text/109-fm-being-interested-in-the-first-result-only.txt:1048,搜「essentially zero」)与第 1060 段(text/109-fm-being-interested-in-the-first-result-only.txt:1060,搜「shape」)。 ↩ ↩2

  8. 出处:「Being interested in the first result only」第 228 段(text/109-fm-being-interested-in-the-first-result-only.txt:228,搜「workhorse」)。 ↩

  9. 出处:「Being interested in the first result only」第 538 段(text/109-fm-being-interested-in-the-first-result-only.txt:538,搜「hello from the proxy」)。 ↩

  10. 出处:「Being interested in the first result only」第 256 段(text/109-fm-being-interested-in-the-first-result-only.txt:256,搜「deadlock」)与第 266 段(text/109-fm-being-interested-in-the-first-result-only.txt:266,搜「true short read」)。 ↩

  11. 出处:「Being interested in the first result only」第 278 段(text/109-fm-being-interested-in-the-first-result-only.txt:278,搜「half-close」)。 ↩

  12. 出处:「Being interested in the first result only」第 282 段(text/109-fm-being-interested-in-the-first-result-only.txt:282,搜「@errorName」)。 ↩ ↩2

  13. 出处:「Being interested in the first result only」第 303 段(text/109-fm-being-interested-in-the-first-result-only.txt:303,搜「task_c2r」)。 ↩

  14. 出处:「Being interested in the first result only」第 350 段(text/109-fm-being-interested-in-the-first-result-only.txt:350,搜「regardless of which side」)。 ↩

  15. 出处:「Being interested in the first result only」第 505 段(text/109-fm-being-interested-in-the-first-result-only.txt:505,搜「limitation」)。 ↩

  16. 出处:「Being interested in the first result only」第 521 段(text/109-fm-being-interested-in-the-first-result-only.txt:521,搜「EXEC:/bin/cat」)。 ↩

  17. 出处:「Being interested in the first result only」第 573 段(text/109-fm-being-interested-in-the-first-result-only.txt:573,搜「Io.Group」)。 ↩

  18. 出处:「Being interested in the first result only」第 619 段(text/109-fm-being-interested-in-the-first-result-only.txt:619,搜「must return void」)与第 669 段(text/109-fm-being-interested-in-the-first-result-only.txt:669,搜「no caller」)。 ↩

  19. 出处:「Being interested in the first result only」第 738 段(text/109-fm-being-interested-in-the-first-result-only.txt:738,搜「fire-and-forget」)与第 758 段(text/109-fm-being-interested-in-the-first-result-only.txt:758,搜「simultaneously」)。 ↩

  20. 出处:「Being interested in the first result only」第 853 段(text/109-fm-being-interested-in-the-first-result-only.txt:853,搜「cancel it cleanly」)。 ↩

  21. 出处:「Being interested in the first result only」第 831 段(text/109-fm-being-interested-in-the-first-result-only.txt:831,搜「silently」)。 ↩

  22. 出处:「Being interested in the first result only」第 909 段(text/109-fm-being-interested-in-the-first-result-only.txt:909,搜「four-byte big-endian」)。 ↩

  23. 出处:「Being interested in the first result only」第 937 段(text/109-fm-being-interested-in-the-first-result-only.txt:937,搜「latency 50」)。 ↩

  24. 出处:「Being interested in the first result only」第 955 段(text/109-fm-being-interested-in-the-first-result-only.txt:955,搜「220 ms」)与第 1018 段(text/109-fm-being-interested-in-the-first-result-only.txt:1018,搜「75%」)。 ↩

  25. 出处:「Being interested in the first result only」第 1024 段(text/109-fm-being-interested-in-the-first-result-only.txt:1024,搜「serializes」)与第 1040 段(text/109-fm-being-interested-in-the-first-result-only.txt:1040,搜「in flight simultaneously」)。 ↩

  26. 出处:「Being interested in the first result only」第 992 段(text/109-fm-being-interested-in-the-first-result-only.txt:992,搜「without a single failure」)。 ↩

  27. 出处:「Being interested in the first result only」第 1072 段(text/109-fm-being-interested-in-the-first-result-only.txt:1072,搜「OS thread pool」)。 ↩

  28. 出处:「Being interested in the first result only」第 1074 段(text/109-fm-being-interested-in-the-first-result-only.txt:1074,搜「explicitly construct」)。 ↩

  29. 出处:「Being interested in the first result only」第 1104 段(text/109-fm-being-interested-in-the-first-result-only.txt:1104,搜「scan every」)。 ↩

  30. 出处:「Being interested in the first result only」第 1110 段(text/109-fm-being-interested-in-the-first-result-only.txt:1110,搜「machinery」)。 ↩

  31. 出处:「Being interested in the first result only」第 1104 段(text/109-fm-being-interested-in-the-first-result-only.txt:1104,搜「scan every」)。 ↩

  32. 出处:「Being interested in the first result only」第 1186 段(text/109-fm-being-interested-in-the-first-result-only.txt:1186,搜「actually ready」)。 ↩

  33. 出处:「Being interested in the first result only」第 1176 段(text/109-fm-being-interested-in-the-first-result-only.txt:1176,搜「without a syscall per operation」)。 ↩

  34. 出处:「Being interested in the first result only」第 1188 段(text/109-fm-being-interested-in-the-first-result-only.txt:1188,搜「no locks」)。 ↩

  35. 出处:「Being interested in the first result only」第 1122 段(text/109-fm-being-interested-in-the-first-result-only.txt:1122,搜「Grand Central Dispatch」)。 ↩

  36. 出处:「Being interested in the first result only」第 1208 段(text/109-fm-being-interested-in-the-first-result-only.txt:1208,搜「fcntl」)。 ↩

  37. 出处:「Being interested in the first result only」第 1260 段(text/109-fm-being-interested-in-the-first-result-only.txt:1260,搜「EV_ADD」)与第 1304 段(text/109-fm-being-interested-in-the-first-result-only.txt:1304,搜「EV_EOF」)。 ↩

  38. 出处:「Being interested in the first result only」第 1202 段(text/109-fm-being-interested-in-the-first-result-only.txt:1202,搜「same structural skeleton」)。 ↩

  39. 出处:「Being interested in the first result only」第 1242 段(text/109-fm-being-interested-in-the-first-result-only.txt:1242,搜「what is ready」)。 ↩

  40. 出处:「Being interested in the first result only」第 1112 段(text/109-fm-being-interested-in-the-first-result-only.txt:1112,搜「worth having」)。 ↩

  41. 出处:「Being interested in the first result only」第 1326 段(text/109-fm-being-interested-in-the-first-result-only.txt:1326,搜「shared memory rings」)。 ↩

  42. 出处:「Being interested in the first result only」第 1352 段(text/109-fm-being-interested-in-the-first-result-only.txt:1352,搜「bit-packing」)。 ↩

  43. 出处:「Being interested in the first result only」第 1394 段(text/109-fm-being-interested-in-the-first-result-only.txt:1394,搜「batch」)。 ↩

  44. 出处:「Being interested in the first result only」第 1416 段(text/109-fm-being-interested-in-the-first-result-only.txt:1416,搜「measurable」)。 ↩

  45. 出处:「Being interested in the first result only」第 1462 段(text/109-fm-being-interested-in-the-first-result-only.txt:1462,搜「sequential benchmark」)。 ↩

  46. 出处:「Being interested in the first result only」第 1500 段(text/109-fm-being-interested-in-the-first-result-only.txt:1500,搜「without a mutex」)。 ↩

  47. 出处:「Being interested in the first result only」第 1658 段(text/109-fm-being-interested-in-the-first-result-only.txt:1658,搜「89.87」)。 ↩

  48. 出处:「Being interested in the first result only」第 1678 段(text/109-fm-being-interested-in-the-first-result-only.txt:1678,搜「12.15」)。 ↩

  49. 出处:「Being interested in the first result only」第 1620 段(text/109-fm-being-interested-in-the-first-result-only.txt:1620,搜「cannot support」)。 ↩ ↩2

  50. 出处:「Being interested in the first result only」第 1678 段(text/109-fm-being-interested-in-the-first-result-only.txt:1678,搜「12.15」)。 ↩

  51. 出处:「Being interested in the first result only」第 1716 段(text/109-fm-being-interested-in-the-first-result-only.txt:1716,搜「warm-up」)。 ↩

  52. 出处:「Being interested in the first result only」第 1754 段(text/109-fm-being-interested-in-the-first-result-only.txt:1754,搜「pull ahead」)。 ↩ ↩2

  53. 出处:「Being interested in the first result only」第 1762 段(text/109-fm-being-interested-in-the-first-result-only.txt:1762,搜「respective hardware」)。 ↩ ↩2

  54. 出处:「Being interested in the first result only」第 921 段(text/109-fm-being-interested-in-the-first-result-only.txt:921,搜「single codebase」)。 ↩

  55. 出处:「Being interested in the first result only」第 923 段(text/109-fm-being-interested-in-the-first-result-only.txt:923,搜「never the bottleneck」)。 ↩

  56. 出处:「Being interested in the first result only」第 931 段(text/109-fm-being-interested-in-the-first-result-only.txt:931,搜「50 ms」)。 ↩

  57. 出处:「Being interested in the first result only」第 1544 段(text/109-fm-being-interested-in-the-first-result-only.txt:1544,搜「only successful connections」)。 ↩

  58. 出处:「Being interested in the first result only」第 1552 段(text/109-fm-being-interested-in-the-first-result-only.txt:1552,搜「client-observable」)。 ↩

  59. 出处:「Being interested in the first result only」第 1512 段(text/109-fm-being-interested-in-the-first-result-only.txt:1512,搜「single line per round」)。 ↩

  60. 出处:「Being interested in the first result only」第 957 段(text/109-fm-being-interested-in-the-first-result-only.txt:957,搜「136.74」)。 ↩

  61. 出处:「Being interested in the first result only」第 960 段(text/109-fm-being-interested-in-the-first-result-only.txt:960,搜「stays roughly constant」)。 ↩

  62. 出处:「Being interested in the first result only」第 1240 段(text/109-fm-being-interested-in-the-first-result-only.txt:1240,搜「fundamental difference」)。 ↩

  63. 出处:「Being interested in the first result only」第 1304 段(text/109-fm-being-interested-in-the-first-result-only.txt:1304,搜「EV_EOF」)。 ↩

  64. 出处:「Being interested in the first result only」第 1220 段(text/109-fm-being-interested-in-the-first-result-only.txt:1220,搜「SO_REUSEADDR」)。 ↩

  65. 出处:「Being interested in the first result only」第 1356 段(text/109-fm-being-interested-in-the-first-result-only.txt:1356,搜「continuation-based」)。 ↩

  66. 出处:「Being interested in the first result only」第 1388 段(text/109-fm-being-interested-in-the-first-result-only.txt:1388,搜「extra syscall round-trip」)。 ↩

  67. 出处:「Being interested in the first result only」第 1350 段(text/109-fm-being-interested-in-the-first-result-only.txt:1350,搜「user_data」)。 ↩

  68. 出处:「Being interested in the first result only」第 1658 段(text/109-fm-being-interested-in-the-first-result-only.txt:1658,搜「89.87」)。 ↩

  69. 出处:「Being interested in the first result only」第 1716 段(text/109-fm-being-interested-in-the-first-result-only.txt:1716,搜「hot state」)。 ↩

  70. 出处:「Being interested in the first result only」第 1948 段(text/109-fm-being-interested-in-the-first-result-only.txt:1948,搜「rounds」)。 ↩ ↩2

  71. 出处:「Being interested in the first result only」第 1864 段(text/109-fm-being-interested-in-the-first-result-only.txt:1864,搜「per-session timeout」)。 ↩

  72. 出处:「Being interested in the first result only」第 1900 段(text/109-fm-being-interested-in-the-first-result-only.txt:1900,搜「active-connection counter」)。 ↩

  73. 出处:「Being interested in the first result only」第 1996 段(text/109-fm-being-interested-in-the-first-result-only.txt:1996,搜「65536」)。 ↩

  74. 出处:「Being interested in the first result only」第 1776 段(text/109-fm-being-interested-in-the-first-result-only.txt:1776,搜「architectural blueprints」)。 ↩

  75. 出处:「Being interested in the first result only」第 54 段(text/109-fm-being-interested-in-the-first-result-only.txt:54,搜「backing buffer」)。 ↩

  76. 出处:「Being interested in the first result only」第 109 段(text/109-fm-being-interested-in-the-first-result-only.txt:109,搜「Task 1 finished first」)。 ↩

  77. 出处:「Being interested in the first result only」第 100 段(text/109-fm-being-interested-in-the-first-result-only.txt:100,搜「timeouts」)。 ↩

  78. 出处:「Being interested in the first result only」第 92 段(text/109-fm-being-interested-in-the-first-result-only.txt:92,搜「focused」)。 ↩

  79. 出处:「Being interested in the first result only」第 1900 段(text/109-fm-being-interested-in-the-first-result-only.txt:1900,搜「active-connection」)。 ↩

  80. 出处:「Being interested in the first result only」第 1864 段(text/109-fm-being-interested-in-the-first-result-only.txt:1864,搜「per-session」)。 ↩

  81. 出处:「Being interested in the first result only」第 1956 段(text/109-fm-being-interested-in-the-first-result-only.txt:1956,搜「variance」)。 ↩

  82. 出处:「Being interested in the first result only」第 915 段(text/109-fm-being-interested-in-the-first-result-only.txt:915,搜「deterministically」)。 ↩

  83. 出处:「Being interested in the first result only」第 913 段(text/109-fm-being-interested-in-the-first-result-only.txt:913,搜「elapsed time」)。 ↩

  84. 出处:「Being interested in the first result only」第 1526 段(text/109-fm-being-interested-in-the-first-result-only.txt:1526,搜「mirror」)。 ↩

  85. 出处:「Being interested in the first result only」第 1352 段(text/109-fm-being-interested-in-the-first-result-only.txt:1352,搜「bit-packing」)。 ↩

  86. 出处:「Being interested in the first result only」第 1398 段(text/109-fm-being-interested-in-the-first-result-only.txt:1398,搜「kernel-crossing overhead」)。 ↩

  87. 出处:「Being interested in the first result only」第 1416 段(text/109-fm-being-interested-in-the-first-result-only.txt:1416,搜「under load」)。 ↩

  88. 出处:「Being interested in the first result only」第 1482 段(text/109-fm-being-interested-in-the-first-result-only.txt:1482,搜「four shared atomic counters」)。 ↩

  89. 出处:「Being interested in the first result only」第 1544 段(text/109-fm-being-interested-in-the-first-result-only.txt:1544,搜「only successful connections」)。 ↩

  90. 出处:「Being interested in the first result only」第 1510 段(text/109-fm-being-interested-in-the-first-result-only.txt:1510,搜「throughput in MB/s」)。 ↩

  91. 出处:「Being interested in the first result only」第 1462 段(text/109-fm-being-interested-in-the-first-result-only.txt:1462,搜「saturate」)。 ↩

  92. 出处:「Being interested in the first result only」第 1072 段(text/109-fm-being-interested-in-the-first-result-only.txt:1072,搜「one less than the logical CPU count」)。 ↩

  93. 出处:「Being interested in the first result only」第 1076 段(text/109-fm-being-interested-in-the-first-result-only.txt:1076,搜「Uring」)。 ↩

  94. 出处:「Being interested in the first result only」第 1784 段(text/109-fm-being-interested-in-the-first-result-only.txt:1784,搜「decouples」)。 ↩

  95. 出处:「Being interested in the first result only」第 177 段(text/109-fm-being-interested-in-the-first-result-only.txt:177,搜「starvation」)。 ↩

  96. 出处:「Being interested in the first result only」第 1212 段(text/109-fm-being-interested-in-the-first-result-only.txt:1212,搜「per-connection buffers」)。 ↩

  97. 出处:「Being interested in the first result only」第 716 段(text/109-fm-being-interested-in-the-first-result-only.txt:716,搜「group.async」)。 ↩

  98. 出处:「Being interested in the first result only」第 278 段(text/109-fm-being-interested-in-the-first-result-only.txt:278,搜「no more data is coming」)。 ↩

  99. 出处:「Being interested in the first result only」第 246 段(text/109-fm-being-interested-in-the-first-result-only.txt:246,搜「intentional and very important」)。 ↩

  100. 出处:「Being interested in the first result only」第 1788 段(text/109-fm-being-interested-in-the-first-result-only.txt:1788,搜「reason about」)。 ↩

  101. 出处:「Being interested in the first result only」第 1776 段(text/109-fm-being-interested-in-the-first-result-only.txt:1776,搜「architectural blueprints」)与第 1788 段(text/109-fm-being-interested-in-the-first-result-only.txt:1788,搜「reason about」)。 ↩

  102. 出处:「Being interested in the first result only」第 1832 段(text/109-fm-being-interested-in-the-first-result-only.txt:1832,搜「10」)。 ↩

On this page