TGTGInsighttelegram intelligenceLIVE / telegram public index
← Welcome to the Black Parade
Welcome to the Black Parade avatar

TGINSIGHT POST

Post #1031

@TheB1ackParade

Welcome to the Black Parade

Views1,320Post view count
PostedApr 704/07/2026, 01:20 AM
Post content

Post content

我们考虑从某个 fd 读消息数据,我们可能会有这样的 API: func ReadInto(msg) { for { epoll_wait(fd) buf = read(fd) copy(buf, msg) } } 如果我们只是为了从几百字节的消息中读几个字节,那么那 copy 是低效的,我们可以实现一个接受回调函数的 ReadFunc,传入的函数只读我们关心的几个字节: func ReadFunc(f func(buf)) { for { epoll_wait(fd) buf = read(fd) f(buf) } } 注意到 ReadInto 和 ReadFunc 都用一套相同的处理 epoll 的代码,我们也是性成熟的工程师了,必须 DRY,于是抽出了一个 readWithPoll,这样两个函数就写成了 func ReadInto(msg) { readWithPoll(func(buf) { copy(buf, msg) }) } func ReadFunc(f) { readWithPoll(func(buf) { f(buf) }) } 由于各种原因,修改 buf 的部分有个 interface 抽象,所以实际上代码是这样的: func ReadInto(msg) { readWithPoll(func(buf) { Interface.Copy(buf, msg) }) } func ReadFunc(f) { readWithPoll(func(buf) { Interface.ReadFunc(f) }) } 我们注意到 interface.Copy 的逻辑其实也可以用 interface.ReadFunc 来实现,出于降低代码复杂度、减少 interface 的目的,我们决定把 interface.Copy 删掉复用 interface.ReadFunc,这样两个代码的调用链、抽象层级、闭包程度也完全一致: func ReadInto(msg) { readWithPoll(func(buf) { Interface.ReadFunc(func() { copy(buf, msg) }) }) } func ReadFunc(f) { readWithPoll(func(buf) { Interface.ReadFunc(func() { f(buf) }) }) } 以上都是软件工程常规,我们先注意到业务代码里只从几百字节的消息里读几个字节,决定做零拷贝优化,实现了 ReadFunc 传入回调函数;然后做 DRY,抽出共用的 readWithPoll 函数;然后降低代码复杂度,删掉了一个接口函数 interface.Copy 复用新增的 interface.ReadFunc,最后代码结构也很一致。 正片开始,问下面哪个调用的性能更好? A. var msg Msg var acc uint64 for { ReadInto(&msg) acc += msg.Count } B. var acc uint64 for { ReadFunc(func(buf) { acc += uint64(buf[:8]) }) } 在 go1.24.4 x86_64 linux6.17 上,结果大吃一精,零拷贝的性能只有全拷贝的一半 28.87 Mops/s -> 55.22 Mops/s,我们优化了半天零拷贝优化了个寂寞。 go playground 请在本地 taskset 锁单核运行: https://go.dev/play/p/kfAeED2Q2mJ 这个问题比我想象中还要棘手,因为它没有明显异常的指标: 1. 两个版本的 perf stat 显示 IPC 高达 4+,不需要做 micro-bench 2. 两个版本的 perf record 对比,cpu 热点函数占比没有明显区别 3. go build -gcflags='-m=2' 对比虽然有一些差异,但直觉上感受这些差异并不应该造成如此严重的性能回退 唯一比较明显的指标是执行的总指令数,零拷贝版本比全拷贝多了一倍,可以断定性能回退正是来自这里;我也大概可以猜测是闭包 capturing、各种 callback escape 导致的,但缺少直观的观测性证据。 这个性能回退是由于我在做某种优化所以一路都在 benchmark 才发现的,如果一个大型项目可能也正在遭遇相似的腰斩性能,但 perf stat 的 IPC 报告和 perf record 的热点报告都无明显异常,我不知道应该如何观测这种性能回归。如果哪位 golang 专家有相关经验,请伸出圆手帮帮男同🥹