2026-10-02 06:00:00
本文永久链接 – https://tonybai.com/2026/10/02/how-to-keep-enjoying-programming-in-the-llm-era
大家好,我是Tony Bai。
【导读】
AI确实能替你把代码写出来,可写代码本身带来的那份满足感,却常常在“甩给Agent”的过程中被悄悄拿走。最近,Haskell社区一位老玩家写了一篇长文,谈自己如何在大量使用AI的同时,仍然守住了编程的乐趣。文章发出后引发了不少共鸣,评论区也出现了一些值得细看的不同声音。
【文章要点】

“你正在滑向AI型倦怠吗?害怕被一个几乎不会编程、毫无质量追求、但坐拥巨额Claude账号的人抢走饭碗吗?对自己项目里的代码质量,甚至‘自己写的’代码质量感到失望?”
这是Haskell核心开发者、单子驱动函数式编程框架 Rhine 的作者 turion,在Haskell官方论坛发帖的开场白。他给这篇长文起了个直白的标题——《How to keep enjoying programming in a world of LLMs》(如何在一个LLM遍地的世界里继续享受编程)。
这句开场白,说出了不少程序员这两年心里的一种隐约不安:AI确实让交付变快了,可那种“把想法亲手敲成代码”的满足感,却常常在一次次“甩给Agent”的过程中被慢慢消磨掉。
turion在帖子里特别强调,自己并不是要打一场“反AI”的嘴仗——前沿大模型背后确实存在不少值得警惕的伦理问题,但那不是这篇文章要讨论的重点,他也提前声明这篇文章百分之百是人手打字、没有借助AI代写。他想回答的是一个更具体、也更私人的问题:代码可以交给AI写,但编程的乐趣,要怎么才能不被一起拿走?
turion给出的不是一句空洞的“少用AI”,而是一整套具体到可以照抄的工作法。核心逻辑很简单:把无聊的、事务性的工作甩给AI,把动脑子、有手感的部分留给自己。
turion把这条摆在最前面,几乎是全文的地基。
他的判断是,如果把编写代码这件事全权交出去,代码库迟早会变成一片“只有Agent自己能活下去的LLM荒原”——人类如果放弃动手写代码,很快就会失去维护这片代码库的能力。更麻烦的是,编程技能是会退化的:只需要几周不亲自动手、事事都交给Agent,你就会发现自己已经很难再重新捡起写代码的手感。
他还补了一刀大实话:LLM生成“能跑”的代码不难,生成人能读懂、后续能维护的好代码却远没有宣传的那么厉害——它们更擅长写“只有它自己以后会继续维护”的代码。你大概率已经体验过那种绝望:面对一整个由AI生成的文件,明知道里面藏着bug,却因为这套代码逻辑对人类来说“完全陌生”,连从哪下手排查都不知道。
turion提出一个很朴素的类比:计算机从诞生之初就是记账工具,AI也不例外,只是现在这个记账工具可以用自然语言对话,而不是填表格。
具体做法是,把开发者之间冗长的讨论、测试结果,丢给LLM,让它整理成可执行的todo清单,再用Markdown文件(带frontmatter)之类的形式做持久化记录——因为当前LLM的上下文窗口再大,一旦塞得太满,也会悄无声息地“丢内容”。
但规划的决策权必须攥在人手里。turion特别强调一条红线:不要让LLM做任何关键决策,而是让它来问你问题。如果你看不懂它提出的问题,说明是AI没给够上下文——或者,也可能是你已经太累了,该歇一歇了。
让Agent去做调研任务时,很容易忍不住去摸鱼——看着它一边“思考”一边检索,顺手再开个新项目支使另一个Agent,或者干脆去接杯咖啡。turion毫不客气地说:这三个选项里,接咖啡反而是最好的那个。
他给出的正确姿势是:你自己也要用搜索引擎同步做调研,至少要把Agent知道的东西也大致过一遍。千万不要把AI调研的结果直接当成事实、直接拍板往下走,这只会埋下让人尴尬的技术债。
他还专门强调了让AI做调研的意义所在:重点不是让它把知识一股脑喂给你、替你做出更好的决定,而是让你不用再对着搜索引擎“Let Me Google That For You”。你应该把这个领域理解到和Agent一样深、甚至更深。
具体操作上:让Agent把调研结果、引用来源都写下来存档;等它带着一个奇怪的方案回来找你时,追问它“调研依据是什么、出自哪个资料”——有大约一半概率,它会自己发现之前的错误;另一半情况,你正好可以借这份资料自己做出判断。
这是turion自己称之为“游戏规则改变者”(game changer)的一节,也是整篇文章的方法论核心。
现在主流的编程Agent工具,几乎都在诱导你走“先规划、再放手让Agent写代码”这条路。turion的建议是:拒绝。
一起规划,但代码你来写。
具体来说:让LLM研究你的代码库,把当前的todo和所有需要改动的地方列出来,提示潜在的坑,帮你回顾相关的调研背景——但真正敲代码这件事,由你自己完成。
他形容自己现在的工作流“非常有意思”,甚至比用AI之前更享受工作:任务清单永远清晰,不用操心整体规划,可以专注在手头这一件事上,因为规划做得好,任务也完成得很快。用他的话说,这就像敏捷开发,但没有那些烦人的流程。
下面这张图,是我们根据turion描述的这套工作流画出的示意图:

turion总结了这套工作流的四个好处:
当然,Agent也有真正能派上用场的场景:清理收尾、小型任务、常规重复劳动、低风险的重构。比如代码里留的FIXME、只写了3个有代表性的case却还剩7个类似的、想给某个模块重组做个benchmark、想把一个不再维护的库换掉——这些都是合适的任务;但从零设计一个复杂系统,通常不适合甩给Agent。
turion也提醒了两个容易踩的坑:
Traversable之类的经典类型类实例。LLM在这类场景下出了名地倾向于复制大段代码,而不是识别出可以抽象的结构,人类要为更好读、更合理的代码把关。turion借用了一个很形象的类比:还记得生成对抗网络(GAN)是怎么让AI生成的图像突然变得逼真起来的吗?一个模型(生成器)负责生成图片,另一个模型(判别器)负责给它打分挑毛病,两者对抗博弈,最终生成器的产出会远好于单独工作时的水平。
把这套逻辑搬到LLM辅助编程上,就是一条自动化审稿流水线:

turion给出一条相当硬的规则:任何LLM产出的东西,不经过一轮自动化审核,就不要接受,甚至不要去读它。
这不仅适用于代码本身(在那些你确实还让它生成代码的场合),也同样适用于规划文档——逐字逐句去挑一份计划里的逻辑漏洞(比如todo 2里要重构的函数,其实到todo 7才会被写出来)实在太费神,不如干脆加一个专门负责审稿的Agent来做这件事。
他还提到一个个人体验:让AI审自己写的代码,出乎意料地有用。
有时候它只是挑几个小毛病,或者非要你多写点Haddock文档注释;但也有不少次,它真的能揪出一个实实在在的bug或疏漏,让你重新聚焦在手头的todo上。他建议大家也在自己的工作里加上这道审稿工序——如果实在不想亲自看AI写的review意见,可以让它自己把那些无关紧要的问题顺手改掉。
有人在帖子下留言:“你这是完全没把前沿模型的能力用起来”。turion的回应很干脆:没错,这恰恰是我这套方法论的必然结果。他列了几条不迷信前沿大模型的理由:
关于token耗尽这件事,turion的态度也很鲜明:不要把“token用完”理解成“自己没算计好、买少了”,而要理解成一次服务中断。厂商卖给你的是“可以使用LLM”这个承诺,一旦兑现不了,那就是他们没有履约——一个session里到底能拿到多少token,是一个不透明、可以被平台随意调整的数字,你很难提前规划。
他给出的应对之道,是把这件事类比成在没有网络的火车或飞机上工作:提前下载缓存好大文件,永远给自己留一份可以离线完成的任务清单。
落到具体做法上,就是前面反复强调的那套流程——始终保有一份规划好的todo清单,token够用时痛快地往前冲,token没了就先去做手头能离线完成的部分,等Agent恢复了,再让它回来打扫战场。
turion在文章后半段专门谈了一个容易被忽视的问题:长期泡在LLM生成的文字里,对人的精神状态是有代价的。
他的原话大意是:LLM生成的文字和人写的完全不是一回事,尤其是当它其实并不真正理解自己在说什么的时候,读起来的观感会更差。要把LLM的车轱辘话,当成对你的精神健康有潜在危害的东西来对待,别摄入太多。
建议是多和真人聊你的代码,尤其是那些更大的愿景、更有意思的细节;只在自动审稿Agent“磨过棱角”之后,再去读AI产出的东西。
除此之外,turion也提醒不要忘了编程本质上是一项社交活动:无论是公司还是开源项目,大家靠PR、issue、commit message互相沟通;哪怕项目里只有你一个人,你的commit记录也是在跟“未来的自己”对话。
千万不要把一份完全由AI生成的PR,原封不动地甩给别人——turion坦言自己就不小心犯过这个错,对方的不满完全合情合理。Agent很乐意帮你把PR描述写得辞藻优美、细节齐全,但turion提醒大家:这不是沟通,这只是工具的输出。
他的建议是把这类文本当成跑分数据或调试日志来对待——可以附在你亲手写的PR描述后面,用<details>折叠起来,让别人自己决定要不要点开看;实际上大部分时候,他们不会点开。
按照这套工作法坚持下来,turion给自己算了一笔账:比不用LLM的时候更高产,具体快多少很难精确量化,两倍左右——这个数字远不如很多“全氛围编程”(full vibe coding)玩家吹嘘的那么夸张,但turion觉得这样挺好。
他给自己打了个比方:自己更像一个使用温和有机肥料的园丁,而那些氛围编程玩家,更像是把地里灌满工业化学品。短期看后者的产量可能更亮眼,但turion相信,自己这种方式从长期看更可持续。
文章的最后,turion写道,自己热爱写Haskell,能靠这个吃饭是梦想成真;就在过去这一个月里,他一度担心这个梦想要成为过去式了——而这篇文章记录的,正是他为了让编程这件事继续值得享受,所摸索出来的一整套方法。目前来看,它管用。
这篇长文之外,评论区里出现的一些不同意见,同样值得放进来一起看——它们让这场讨论没有停留在“turion一个人的经验”。
不同的起点:AI让我第一次实现了过去做不到的想法
一位Haskell核心库维护者看到标题的第一反应,恰恰和turion相反:他好奇的是“如果哪天失去LLM,我还能不能继续享受编程”——因为LLM已经让他能更轻松地把想法落地,把编程里枯燥的体力活都甩给AI,只留下有趣的部分给自己。有意思的是,他读完全文后发现,自己其实早就在无意识地践行文中的不少建议了。
对于turion“token焦虑等于被平台背叛”的说法,这位开发者也提出了质疑:他自己用的是OpenAI的月付计划,额度和时限都是透明的,可以随时用/status命令查看,“背叛”这个词会不会用得太重了?他还调侃说,这让他想起伍迪·艾伦那句老段子——“这儿的饭真难吃”,“是啊,分量还特别少”——似乎在说,一边担心AI烧脑过度,一边又抱怨AI给的不够多,这两种情绪放在一起本身就有点矛盾。
turion随后澄清,自己的措辞确实带点情绪化,他真正想表达的是:当你把整套工作流建立在某个AI套餐之上,额度说变就变,工作流可能瞬间被打乱——他甚至没能查到Anthropic的Claude Code团队席位到底包含多少token,只能假设这是一个可以被厂商随意调整的数字。他提出一个更明确的“公平市场”标准:要有独立开源工具能测算token消耗量、要有明确数字且能保证一段时间(至少几个月)内不变的套餐、要有可靠的正常运行时间保证——据他所知,目前没有任何一家厂商能同时满足这三条。
一个具体场景:核心库维护到底适不适合用AI
评论区里最扎心的交锋,出现在“AI能不能干好包维护这种脏活”上。一位用户抛出一个具体问题:在类似Haskell核心库委员会issue #411这种任务上,LLM表现如何?
帖子作者turion的合作者 hasufell 给出了相当保留的回答:在“底层原语”和“奇怪API”(比如Windows、PowerShell)这类场景下,他的体验用一个词概括——不及预期。而且他也只把LLM当搜索工具来用——因为这些模型并不真正理解PowerShell,想让它正确处理进程调用之类的细节,往往需要人自己反复琢磨、大量试错;LLM还特别容易收敛到某种“大家都这么干”的通用变通方案,而不是真正最优的解法——这对于维护一个核心库来说,恰恰是最危险的倾向。
hasufell补充的另一点,也很值得程序员们警惕:LLM会悄悄消解掉你对自己判断的那种不安全感——比如当你其实隐约觉得自己没完全想清楚某个决定的全部后果时,AI给出的那种“表面自信”,反而可能带来负面后果。
关于团队分工的讨论:要不要允许“AI派”和“反AI派”共存
hasufell还提出了一个更宏观的期待:希望业界未来能走到这样一种状态——公司愿意雇用具有不同“AI使用习惯”的工程师,让他们在同一个团队里协作。这件事操作起来并不容易(比如对坚决“零AI”的人来说会比较有挑战),但他相信是有办法实现的:系统的不同部分、不同角色,或许适合不同的AI使用尺度,只是这个边界具体怎么划,需要认真做政策设计——他举了个例子,自己更倾向于让不用AI的人来做代码评审,但这同样需要配套的政策(比如干脆禁止自动生成的commit message),否则容易把这部分人“逼到职业倦怠”。
他还提到,在开源世界里,围绕LLM是否可以抓取、使用开源代码这个问题,社区已经开始出现分裂——他举了Sourcehut与Codeberg近期相继调整服务条款、限制LLM抓取的例子,并认为这种分化“挺好”。
把整条讨论串完整看下来,其实很难简单地站队“挺AI”还是“反AI”——这场讨论真正的价值,在于它把一种很多人都有过、却说不清楚的感受(“用AI写代码之后,好像没那么开心了”),拆解成了具体、可操作的工作流问题。对国内的程序员和团队来说,至少有三点值得琢磨:
这场发生在Haskell论坛里的讨论,或许不会给出一个所有人都认可的标准答案,但它至少提出了一个值得每个程序员认真回答的问题:当AI可以替你写代码的时候,你到底还想不想自己写?
参考资料:How to keep enjoying programming in a world of LLMs,Haskell Community Discourse论坛,作者turion及回帖网友,发布于2026年9月
还在为写 Agent 框架频频死循环、上下文爆炸而束手无策?我的新专栏 《从0 开始构建 Agent Harness》 将带你:
扫描下方二维码,开启从 0 开始构建Agent Harness 的实战之旅。

还在为“复制粘贴喂AI”而烦恼?我的新专栏 《AI原生开发工作流实战》 将带你:
扫描下方二维码,开启你的AI原生开发之旅。

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

2026-09-30 06:00:00
本文永久链接 – https://tonybai.com/2026/09/30/tailscale-faster-go-performance-optimization
大家好,我是Tony Bai。
【导读】
Tailscale 是一家由 Go 核心团队多位前成员创立、几乎全栈用 Go 语言构建的网络公司,最近又在官方博客上“秀肌肉”了。这一次,他们没有谈 NAT 穿透,也没有谈零信任架构,而是把镜头对准了一个更朴素也更硬核的问题:数据包,怎么才能跑得更快?
9 月 22 日,Tailscale 官方发布博客《We’re making Tailscale faster》,详细拆解了即将在 v1.104 及后续版本中上线的一整套性能优化:更省内存的小包处理、面向子网路由器 / 应用连接器 / 出口节点的多队列架构、利用 writev 减少内存拷贝,以及能让弱网环境下启动速度提升一到两个数量级的“网络地图缓存”。这些优化背后,几乎每一处都是 Go 语言在系统编程、并发调度和内存管理上的“活教材”。
本文将结合 Tailscale 官方博客原文及其关联的历史优化文章,系统梳理这次提速方案的技术细节、设计取舍,以及它对 Go 语言网络编程实践的启发。
【文章要点】

如果你关注 Go 语言生态,大概率听说过 Tailscale 的名字。它的几位核心创始人和早期工程师,曾是 Go 团队在 Google 内部的成员,离开后创立了 Tailscale,用 Go 语言重新实现了一套基于 WireGuard 的零配置组网方案。可以说,Tailscale 从诞生第一天起,就是 Go 语言在真实世界高性能网络基础设施中的一块“活广告牌”。
Tailscale 的核心竞争力之一是 NAT 穿透——即便在各种“不友好”的网络环境下,也能帮设备找到彼此的直连路径。但光“连得上”还不够,“连得快”同样重要。这也是本文要聊的主题:数据平面(data plane)的性能优化。
按照官方博客的说法,过去几年 Tailscale 在这条路上已经达成不少里程碑:
这些积累,让 Tailscale 逐渐具备了支撑 CI/CD、Agentic 工作流、远程开发环境、机器人边缘设备、大规模遥测数据传输等“性能敏感型”场景的能力。而这一次的博客,就是团队交出的最新答卷。
网络世界里有个残酷的现实:大多数数据包都很小,往往只有 1 KiB 左右。但要用上 Linux 最高效的吞吐工具——比如通用接收卸载(Generic Receive Offload,GRO)——Tailscale 就必须一次性准备好接收 64 KiB 的数据。
官方博客用了一个很形象的比喻:这就像集装箱运输,港口、货轮、卡车都是按照统一的集装箱规格设计的,不管里面实际装了多少货物。
问题在于,Tailscale 依赖的 wireguard-go 实现,只提供一种 64 KiB 大小的缓冲区用于“拆箱”——每个数据包都要被解密并单独投递。这意味着,一个 1 KiB 的小包,每次都要完整复制进一个 64 KiB 的缓冲区里,造成大量空间浪费和不必要的内存拷贝。这是一个典型的、值得深挖的优化点。
Tailscale 的做法是:在 Linux 和 Android 平台上,不再搬家,而是原地标注。直接记录每个数据包在大缓冲区里的起止位置,多个小包可以共享同一块分配的内存,不需要各自另起炉灶。

上图展示了优化前后的对比:优化前每个包各占一个 64 KiB 缓冲区(大量空间浪费),优化后多个包被打包进同一块缓冲区并用分隔标记区分。
这项改动本身,就在多种网络配置下带来了约 5% 的速度提升。此外,团队还顺手缩短了数据包在流水线各阶段之间的队列长度——测试发现,绝大多数队列深度根本用不上,缩短队列既减少了等待时间,也降低了内存开销。
省下来的内存去哪了?官方的回答很直接:优先分给最“累”的那批节点——子网路由器和应用连接器。这两类节点,正是下一节的主角。
子网路由器(subnet router)在不同 tailnet(Tailscale 网络)里的负载可能天差地别。一个小型家庭实验室的子网路由器,可能只需要转发几个 192.168.x.y 设备的流量;而一个前置云端部署、连接着数百个节点的子网路由器,承载的流量则完全是另一个量级。
在此之前,子网路由器、应用连接器、出口节点处理数据包的方式,是把多个互不相关的连接塞进同一条有序的单线程流水线。为什么要这样设计?因为接收端的应用绝不能看到自己的数据包乱序到达——这是网络协议栈的一条基本约束。但代价是,无论后端有多少 CPU 核心,所有连接都要挤在同一条车道上排队。
既然前面已经省出了内存空间,团队顺势实现了一套多队列系统:车道数量不再固定为 1,而是根据机器资源动态伸缩,与 peer 数量解耦。每一条数据流固定分配到一条车道上,多条车道并行运行,工作负载因此能真正铺开到多个 CPU 核心上。
优化前,所有包依次经过一个 Reader、四个 Crypto 阶段、一个 Writer 后到达 Devices,整条流程是单线的:

优化后,四组并行的 Reader-Crypto-Writer 流水线分别处理不同数据流,最终汇合到 Devices:

效果是什么?子网路由器和应用连接器的聚合吞吐能力提升,收发数据包之间的延迟降低,同一台机器上已有的硬件资源被更充分地利用。对于那些通常服务大量短连接用户的应用连接器和出口节点来说,这种提升尤其明显。
Tailscale 技术团队成员 Alex Valiushko 在博客中这样总结这项改动的意义:
这意味着更低的延迟,本质上是从数据在网卡上被读取到的那一刻,到被发送给操作系统的那一刻之间,处理速度变得更快了。
第三项优化听起来更“底层”,也更 Linux 系统编程一些:Tailscale 客户端开始用上 Linux 的 writev 系统调用。
writev 里的 v 代表“vector(向量)”——它允许 Tailscale 把多段分散在内存中的数据,一次性描述给内核,而不需要先把这些数据拷贝、拼接成一块连续内存,再传给内核。换句话说,Tailscale 只需要告诉内核“这几块数据分别在哪里、有多大”,剩下的搬运工作交给内核一次性完成。
这带来的直接收益是:内存中数据包拷贝次数更少、写操作次数更少、吞吐量更高。这是一个很经典的“减少系统调用次数 + 减少内存拷贝”的性能优化范式,在高性能网络编程中屡见不鲜,Tailscale 这次算是在自己的场景里把它落到了实处。
前三项优化都聚焦在 Linux / Android 平台的数据平面吞吐上。而第四项优化——netmap caching(网络地图缓存)——瞄准的是一个几乎所有平台都会遇到的问题:启动延迟。
一台设备加入 Tailscale 网络时,通常要先连接 Tailscale 的控制面(control plane),完成身份认证,拿到一份描述“我能连到哪些设备、怎么连”的“网络地图”(netmap)。网络状况良好时,这个过程在 100 毫秒左右就能完成,用户几乎感觉不到延迟。
但现实往往没那么美好。飞机上糟糕的 Wi-Fi、公司里严格的网络过滤,或者其他各种不给力的网络环境,都可能让设备迟迟连不上控制面——问题出在哪还不容易被发现,用户只会感受到一个结果:连不上其他设备。
即便在理想网络条件下,对于一些延迟极度敏感的工作负载来说,100 毫秒的启动延迟也可能显得过长。
网络地图缓存要解决的正是这个问题。启用后,tailnet 中的每台设备都会在本地磁盘上保存一份网络地图的副本。设备启动时,可以先用这份缓存的“旧地图”与其他设备建立连接,与此同时在后台继续尝试联系控制面获取最新信息。这些连接依然是设备之间直接协商完成的,Tailscale 本身不会看到任何流量内容,这一点和平时并无区别。
当然,这项功能也有边界条件:
Tailscale 技术团队成员 Claus Lensbøl 这样描述这项功能的价值场景:
网络状况不佳,才是网络地图缓存真正能发挥大用处的地方。设备客户端会说,我们还没联系上控制面,但大概率很快就能联系上,与此同时,你已经可以开始做一些事情了。
根据官方博客披露的数据,在控制面可达性较差的 tailnet 中,“热启动”(使用缓存)相比“冷启动”,数据面开始可用的速度能快一到两个数量级。对于那些启动延迟波动大,或者物理上离 DERP 中继服务器 / 控制面较远的设备,这项改进的实际体验提升会非常明显。
官方博客给出了一份相对清晰的时间表:
| 优化项 | 平台 | 预计节点 |
|---|---|---|
| 内存开销降低(缓冲区优化) | Linux / Android | v1.104 客户端 |
| 多队列(子网路由器 / 应用连接器受益) | Linux / Android | v1.104 之后的版本 |
吞吐量提升(含 writev 等) |
Linux / Android | 2026 年春季已部分实现,完整收益在 v1.104 之后的版本释放 |
| 网络地图缓存 | 桌面端(当前已可通过功能开关启用);移动端 | v1.104 默认启用(桌面);v1.104 之后(移动端) |
博客的最后一部分,Tailscale 团队坦诚地抛出了一个行业性的痛点:性能问题依然很难诊断和测试。他们列出了当前主流性能测试工具存在的几个共性缺陷:
因此,Tailscale 正在探索一套面向自身架构的原生监控与测试工具,并公开征集用户反馈,帮助定义这套工具的方向。
回看这篇博客,会发现 Tailscale 这一次并没有讲什么“颠覆式”的新概念,恰恰相反,四项优化几乎都是网络系统编程里的“经典动作”:减少不必要的内存拷贝、用多队列 / 多核并行替代单线程瓶颈、用向量化系统调用减少内核态往返、用本地缓存缩短启动延迟。
真正值得学习的,是这套“组合拳”背后的工程方法论:先解决内存问题,腾出空间,再用腾出来的资源去解决并发问题;先看清瓶颈发生在哪个具体场景(子网路由器 vs 应用连接器 vs 出口节点),再对症设计架构;在做数据面优化的同时,也没有忽视“看不见摸不着”但同样重要的启动延迟和可观测性问题。
对于用 Go 构建高性能网络基础设施的团队来说,这篇博客值得当作一份实战案例反复咀嚼——它告诉我们,性能优化很多时候不需要“炫技”,把最朴素的系统编程原理,扎扎实实落到每一个具体场景里,就已经足够“快”了。
还在为写 Agent 框架频频死循环、上下文爆炸而束手无策?我的新专栏 《从0 开始构建 Agent Harness》 将带你:
扫描下方二维码,开启从 0 开始构建Agent Harness 的实战之旅。

你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?
继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!
我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。
目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

2026-09-29 06:00:00
本文永久链接 – https://tonybai.com/2026/09/29/go-import-path-decouple-github-hn-debate
大家好,我是Tony Bai。
【导读】
一行 import "github.com/...",到底是无伤大雅的习惯,还是埋了多年的技术债?近日,《Don’t couple your Go code to GitHub》登上 HN,评论区吵成一团:有人说全局替换五分钟搞定,有人说自己的团队为此折腾了数周。谁对?我们把双方的论据摊开来看。
【文章要点】

近日,开发者 Iain Cambridge 发表了《Don’t couple your Go code to GitHub》,随后登上 HN 热门。发稿时,它已经拿到近 300 点、近百条评论。
原文观点很直接:Go 用代码的获取地址直接充当命名空间,如果这个地址就是 github.com/xxx,代码就和托管平台绑死了。
他见过一家同时使用 GitLab、GitHub 和 Azure DevOps 的公司,不是业务需要,而是路径迁移的工作量太大,没人“有时间”做,于是三个平台一起付费。
他的解法是使用自己的域名,比如 go.uber.org、go.mongodb.org 这种形式,再由域名指向真实仓库。日后换托管平台,用户的 import 一行都不用改。
但真正有意思的,是评论区。这条帖子几乎把 Go 社区在这个问题上的分歧一次性暴露了出来。今天我们不做“布道”,而是把这场辩论完整地梳理一遍:反方到底说对了什么?正方又有哪些是反方没看到的?
要判断谁对,先要理解 import path 在 Go modules 里承担了什么。它同时是三样东西:

评论区里有位开发者说得很到位:其他语言也有类似问题,但 Go 特别容易“踩坑”。因为 Go 以目录为包,项目稍微大一点,就要拆目录;而拆了目录,最顺手的写法就是在源码里写下完整的 github.com/... 路径。工具链是在引导你把托管地址写进代码。
同一个字符串,既是名字,又是地址。把会变的东西,焊在了不该变的东西上。这是争论的起点。
这是最高频的质疑:所有路径都指向 github.com,查找替换一遍,最多几个小时的事。有人还补刀:让编程 Agent 去做不就好了?
对当前这个仓库来说,这没错。问题出在版本历史。
评论区里,一位亲历过大规模迁移的开发者,给出了一个非常经典的推演:
libfoo 有 v1.2.0 和 v1.3.0 两个版本;authservice 依赖 libfoo v1.2.0,apiservice 依赖 libfoo v1.3.0;你把三个仓库的最新代码都改了。但 libfoo 的 v1.2.0 和 v1.3.0 这两个 tag,是迁移之前的提交,里面仍然引用 github.com。要让依赖它们的服务继续构建,就得基于这两个 tag 各拉一个分支,重新替换、重新打 tag。

三个仓库、简单的依赖关系,就要做 7 次替换。这位开发者的结论是:规模一大,数字会变得非常可怕,最终留下的还是一个二分定位(bisect)被破坏、旧版本几乎无法重新构建的代码库。
所以,“全局替换”这个论点的盲区,是它只处理当前快照,没有处理版本历史和依赖图。这不是 Agent 能力的问题,而是不可变的 tag 携带了旧路径。
go.mod 的 replace 不就行了?”另一批人的方案更优雅:不改源码,在 go.mod 里写一行:
replace github.com/example/example => gitlab.com/example/example
这确实是 Go 工具链提供的正规能力,对最终应用来说很好用。但它有两个限制:
第一,replace 只在主模块的 go.mod 中生效。 如果你是库作者,你的下游使用者不会受益,因为依赖模块里的 replace 会被忽略。评论区里有人问得很直接:那些使用你代码的其他人呢?难道让他们都各自加 replace?

第二,源码里仍然是旧地址。 评论区有人反复追问:这样做,你的所有源文件里,将永远留着指向废弃基础设施的 URL,这是你想要的长期状态吗?
所以 replace 是止血手段,不是架构方案。它解决的是“今天能构建”,不是“路径不再被平台绑架”。
这是反方最有力的一击,也是我认为正方需要认真回应的一点。
评论区的原话大意是:GitHub 几乎不会消失,而自有域名一旦停止续费就没了,对个人开源开发者来说,这个概率比 GitHub 消失更高。
我们必须承认:这个风险是真实的。
用自己的域名做 import 路径,等于把身份的最终解释权,收归了这个域名。域名被抢注,别人就能对你所有模块路径“说了算”,这已经是供应链安全问题。
但也有几个反驳角度,值得放在一起看:
所以这条争论的诚实结论是:域名不是免费的午餐,它是用“可控的运维成本”换“不可控的迁移成本”。对个人小库,未必划算;对有长期维护计划的团队和项目,通常划算。
有评论认为,为了未必会发生的迁移,提前配置域名,是过早优化。
这个论点在“概率”上成立,在“成本结构”上不成立。解耦的成本是不对称的:
| 时间点 | 做一次的成本 |
|---|---|
| 第 0 天(新项目) | 一个域名+几行配置,近乎为零 |
| 第 N 天(已有大量 tag 与依赖方) | 需要处理历史 tag、协调下游、破坏二分定位 |
这类似于“要不要从一开始就使用数据库迁移工具”:大概率你很久用不上,但一旦需要,早期没做的代价会被放大成数量级的差距。它不是过早优化,而是一份便宜的期权。
评论区还有一个很有启发性的视角:自有域名的意义,不只是应对迁移。
一旦所有 import 都经过你的域名,你就可以在未来的任何时刻,把这个域名指向你的制品仓库或私有代理。这意味着依赖拉取有了统一入口,你可以逐步做到缓存、版本锁定、校验和检查、统一鉴权。评论区里有人认为,这将成为软件供应链安全的基本盘。
这也顺带回答了那场“该不该 vendor 依赖”的旁支争论:无论你选择 vendor、代理镜像还是直连,有一个自己控制的间接层,选择权就在你手里。
聊完辩论,看看机制本身。当你执行 go get go.yourcompany.com/mylib,背后发生的是:

关键就是这个 meta 标签:
<meta name="go-import"
content="go.yourcompany.com/mylib git https://github.com/yourorg/mylib">
三段内容依次是:导入路径前缀、版本控制系统、真实仓库地址。之后只要把第三段改成 GitLab 或自建服务器地址,所有 import 语句都不必变动。
这也是 go.uber.org/zap、k8s.io/client-go、google.golang.org/grpc 这些知名库,路径中不含 github.com 的原因。
Iain 原文给了一套最小实现:对带 ?go-get=1 的请求返回上面的 HTML,对普通浏览器访问则直接跳转到真实仓库,这样人类访问也不会看到空白页。核心 HTML 如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="go-import"
content="go.example.com/mylib git https://github.com/yourorg/mylib">
<meta name="go-source"
content="go.example.com/mylib https://github.com/yourorg/mylib https://github.com/yourorg/mylib/tree/master{/dir} https://github.com/yourorg/mylib/blob/master{/dir}/{file}#L{line}">
</head>
<body></body>
</html>
其中 go-source 是给 pkg.go.dev 之类的文档站点用的,告诉它源码目录、文件、行号的链接怎么拼。
这里有一个评论区里被指出的细节:原文的 Nginx 配置用的是 301 永久重定向。浏览器会永久缓存 301。如果日后你想把人类访问的跳转目标改成别处,曾经访问过的浏览器仍会被带去旧地址。对 go 命令和 curl 没有影响,但对浏览器访问有影响。
建议:给人类访问的跳转使用 302,尤其在你还没有完全确定最终托管地址的阶段。这个细节小,但恰好是“迁移灵活性”这个初衷本身要求的。
对个人项目、少量模块,静态方案完全够用。但进入团队场景,会遇到:
1. 一个模块一份配置。
按这套配置,每个模块都需要对应的目录和 index.html。10 个还好,100 个就是 100 份几乎一样的文件。更现实的需求是“go.corp.com/dept/* 统统映射到 git.corp.com/dept/*.git”,一条规则覆盖一个前缀。
2. 主版本后缀 /v2。
Go modules 规定主版本 ≥ 2 的路径必须带 /v2、/v3 后缀。go.example.com/mylib/v2 也要能被解析到同一个仓库。很多团队是在第一次发 v2 时,才发现解析服务少了这一环。
3. 私有仓库。
私有模块需要配置 GOPRIVATE,让 go 命令绕过公共代理与校验数据库:
export GOPRIVATE=go.yourcompany.com
这里有一个值得强调的原则:vanity 服务只负责告诉你“去哪拉”,真正的鉴权应始终发生在你与 Git 服务器之间。一个设计良好的方案,不应该经手你的仓库密码。
4. monorepo。
一个仓库里放多个 Go 模块很常见。传统 go-import 只能指向仓库根目录;Go 1.25 起,go-import 增加了对子目录的支持,让同一仓库中的多个模块可以各自拥有独立的 vanity 路径。这属于较新的能力,不少存量方案尚未跟进。
| 维度 | 自建静态(Nginx+HTML) | 自建服务或站点模板 | 托管服务 |
|---|---|---|---|
| 上手成本 | 最低 | 中,需要部署 | 低,CLI 添加路由 |
| 模块数量多 | 靠脚本生成,维护痛 | 配置集中管理 | 通配规则一条覆盖 |
/v2 等主版本 |
需手动补 | 视实现而定 | 视实现而定 |
| 域名与 TLS | 自己搞定 | 自己搞定 | 通常托管 |
| 运维负担 | 中 | 中高 | 低 |
| 适合 | 个人、小团队 | 要求全掌控的团队 | 不想维护基础设施的团队 |
其中第二类,评论区里就有开发者分享了自己做的 Hugo 主题 hugo-theme-govanity,用静态站点生成器批量产出 vanity 页面。同时他也坦言,自己仍在摸索如何在多个托管平台间并行维护,对自有域名的依赖也真实存在,但权衡后仍然倾向于这么做。
第三类是托管服务。我自己做的 gvu(GoMod Vanity URLs)属于这一类,它针对上面这些问题的思路是:
# 安装 CLI
curl -fsSL gomodvanityurls.com/install.sh | bash
# 添加路由:自定义路径 → 真实仓库
gvu add go.yourcompany.com/private-module https://github.com/yourorg/private-module.git
/v2、/v3 无需额外配置;--subdir 路由同一仓库中的多个模块。选哪一类,取决于你的模块数量、运维人力和对外部依赖的容忍度。重要的不是用谁,而是你有没有这一层间接层。
辩论到这里,我们可以给出一个不那么“一刀切”的判断:
| 场景 | 建议 |
|---|---|
| 个人练手项目、一次性工具 | 不必折腾,直接用 github.com 即可 |
| 会被别人依赖的开源库,且你有长期维护打算 | 强烈建议从第 0 天就使用自有域名 |
| 企业内部库,尤其是多团队共用的 | 强烈建议,并配合自建 GOPROXY |
已有大量 github.com 路径的存量项目 |
至少让新模块用上域名,存量按节奏迁移 |
这个表的关键词是:依赖方数量与生命周期。依赖你的人越多、代码活得越久,越应该早做。
如果已有存量模块,请记住评论区那条血泪教训:旧 tag 不会因为你改了主干而自动变成新路径。

关键操作:
# 修改模块声明
go mod edit -module go.yourcompany.com/mylib
# 批量替换 import(macOS 的 sed 需写成 sed -i '')
find . -name '*.go' | xargs sed -i 's#github.com/yourorg/mylib#go.yourcompany.com/mylib#g'
go mod tidy
go build ./... && go test ./...
三个务必注意的细节:
go.mod 声明了新路径,用旧路径拉取这些新版本会报错,提示声明的路径与请求路径不一致。所以旧版本会一直留在旧路径上,依赖方升级到新版本之前不受影响,这既是保护,也是遗留问题的来源。go.mod 里,用 // Deprecated: 注释标注新路径,依赖方可以通过 go list -m -u 之类的工具看到。这次迁移会有成本,但它是最后一次。此后换 GitLab、换自建 Git、换 IP,依赖方的 import 都纹丝不动。
回看整场辩论,双方其实都没错,只是站在了不同的时间点:
所以这场争论的本质,不是“要不要解耦”,而是:你愿意在第 0 天花半小时买一份便宜的期权,还是愿意在第 N 天,为一次跨团队、不可灰度的迁移买单?
Go 没有中心化的包仓库,这份自由让生态轻盈,也把一个决策交给了每个开发者:你的代码,叫什么名字?
在写下第一行 import 之前,多花一点时间想清楚它叫什么,可能是你今年 ROI 最高的一次架构决策。
你的团队现在的 import 路径,是 github.com,还是自己的域名?欢迎在评论区聊聊你的经历。
参考资料
还在为写 Agent 框架频频死循环、上下文爆炸而束手无策?我的新专栏 《从0 开始构建 Agent Harness》 将带你:
扫描下方二维码,开启从 0 开始构建Agent Harness 的实战之旅。

你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?
继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!
我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。
目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

2026-09-28 06:00:00
本文永久链接 – https://tonybai.com/2026/09/28/go-vs-rust-portable-simd-2026
大家好,我是Tony Bai。
【导读】
2026年9月24日,Go官方博客发布了一篇关于Go 1.27中SIMD支持进展的文章;几乎同一时间,Rust社区资深开发者Sergey Davidoff也放出了年度重磅长文,系统盘点了Rust生态里SIMD编程的现状。两篇文章单独看都是硬核的“工程八卦”,放在一起看,却像是两个语言阵营对同一道难题交出的两份完全不同的答卷:如何既能“一次编写、到处飞快”,又不必人人都去写汇编。
【文章要点】

如果你不写高性能计算相关的代码,SIMD(Single Instruction Multiple Data,单指令多数据)这个词可能有点陌生。但它其实无处不在:图片解码、字符串搜索、哈希计算、向量数据库的相似度检索、甚至Go自己的垃圾回收器,背地里都在偷偷用它加速。
简单说,普通CPU指令一次只能处理一个数:把两个数加起来,占用一条指令;但SIMD指令可以让CPU一次性把8个、16个甚至64个数同时加起来,几乎是“同样的时间,多干好几倍的活”。问题是,这份“福利”长期以来只有写汇编或者调用平台专属指令集(Intrinsics)的人才能真正吃到嘴里,普通业务代码很少能沾光。
于是,“怎么用一种优雅、可移植的方式暴露SIMD能力”,就成了几乎所有系统级编程语言都要面对的共同难题。而就在这个九月,Go和Rust两个阵营几乎同时交出了各自的最新答卷——但答案却南辕北辙。
先简单铺垫一下背景。
现代CPU的算术运算单元其实非常廉价,一颗芯片里塞满了加法器、乘法器,但真正的瓶颈在“指令译码”这个环节——CPU一次只能高效地译码和调度有限数量的指令。SIMD的思路就是:既然算术单元这么多,不如一条指令喂给它一整“批”数据,而不是一个一个数地喂。
于是同样是做加法,标量(Scalar)指令一次只加一对数,SIMD向量(Vector)指令一次可以加8对、16对甚至更多,理论上能拿到数倍到数十倍的吞吐提升。

但天下没有免费的午餐,SIMD的“坑”主要来自硬件的碎片化:
正是这种“同一个概念,十种实现方式”的现状,逼着Go和Rust都不得不认真面对“可移植SIMD”这道题。
simd包archsimd与simd
Go的解法分成了两层。
第一层是Go 1.26就已经引入的archsimd包:这是一个架构相关(architecture-dependent)的API,amd64、arm64(NEON)、wasm各有各的实现,力图尽量贴近底层指令,性能拉满,但代码要按平台分别编译。
第二层,也是这次Go 1.27真正的主角——全新的、实验性的simd包。它的设计思路松散地借鉴了C++的高性能SIMD库Highway,目标是提供一套完全平台无关、向量宽度无关的接口。换句话说,写一份simd代码,不用关心自己到底跑在AVX2还是NEON上,编译器会在背后把它落地成最匹配当前硬件的实现;如果目标平台压根没有SIMD硬件,simd包还会自动退化为纯软件模拟,保证代码“总能跑起来”,只是快慢有别。

要启用这个实验特性,需要在编译时加上GOEXPERIMENT=simd。
Go团队在文章里花了不小篇幅解释“为什么这事儿这么难”。差异主要体现在三个维度:
向量宽度不统一:wasm、PowerPC、s390x只有固定的128位向量;amd64同时支持128、256、512位;RISC-V的向量宽度甚至在128到65536位之间浮动,且要运行时才能确定;ARM64更是“一鱼两吃”,NEON固定128位,SVE则是128到2048位的可变宽度。
掩码(Mask)机制五花八门:SIMD里经常需要“按条件只对部分元素生效”的操作,不同架构实现这件事的方式完全不同——有的靠位运算模拟掩码(wasm、AVX、AVX2、NEON),有的有专门的掩码寄存器、一个比特对应一个向量元素(AVX512、RVV),SVE则是一个比特对应一个字节,情况相当复杂。
具体指令支持参差不齐:比如wasm就不支持64位整数向量的比较操作;同一个架构内部,还要看具体的“特性位”(feature)才能确定某条指令到底存不存在。
面对这种碎片化现实,Go团队给simd包定下的设计目标是:
具体做法是:
simd包的基础能力;ReduceSum)。官方给出的一个典型例子是向量内积计算,思路很直白:一次装载一批float32,用MulAdd(乘加)指令批量计算,循环结束后再处理不足一批的“零头”:
// innerProduct returns the inner product of x and y.
func innerProduct(x, y []float32) float32 {
var a simd.Float32s
var i int
for i = 0; i < len(x)-a.Len()+1; i += a.Len() {
u := simd.LoadFloat32s(x[i : i+a.Len()])
v := simd.LoadFloat32s(y[i : i+a.Len()])
a = u.MulAdd(v, a)
}
if i < len(x) {
u, _ := simd.LoadFloat32sPart(x[i:])
v, _ := simd.LoadFloat32sPart(y[i:])
a = u.MulAdd(v, a)
}
return sum(a)
}
注意这里全程没有出现任何“256位”或者“AVX2”字样,a.Len()会在运行时返回当前平台实际的向量长度——这正是“平台无关”的核心体现。当然Go 1.27的第一版还有些功能缺口,比如横向求和ReduceSum要等到下一个版本才补上,目前示例里还得手写一个辅助函数。
这一层“看不见的魔法”值得单独说一说。Go团队坦言,simd其实同时是一个包、一个内部实现包,外加编译器前端的一段AST(抽象语法树)重写逻辑。
编译器会为所有涉及simd类型的函数、变量和类型生成多份"特化副本",分别对应128位、256位、512位向量或者纯模拟这几种情况,并在函数名后面加上类似@simd256这样的后缀。
程序启动时先检测硬件实际支持的SIMD级别,之后统一走到对应的特化版本,特化函数之间互相调用不再需要额外的判断开销,甚至可以直接内联。这算是一种在“代码体积”和“运行时性能”之间的折中:把判断开销尽量往调用链上层推,但又不会推得太高,SIMD计算内部完全没有额外分支负担。
为了方便开发者在没有对应硬件的情况下也能测试各种SIMD路径,Go还提供了一组GODEBUG=simd=...开关,比如强制走纯模拟(simd=0)、强制使用128/256/512位向量、甚至允许“明知道某些特性缺失也硬上”(用+前缀),方便针对Raspberry Pi、Apple Silicon的amd64模拟环境这类“能力不全”的场景做兼容性测试。
如果说Go的策略是“编译器亲自下场,标准库统一收口”,那么Rust给出的答案完全是另一种画风——生态百花齐放,至今没有收敛出一个“官方唯一答案”。
Sergey Davidoff(社区里更熟悉的名字是Shnatsel,Rust安全代码工作组负责人)这篇《2026年Rust SIMD生态现状》,正是延续他去年同名系列的年度盘点。有意思的是,他这次不再是纯粹的“第三方观察者”——去年写完调研之后,他一头扎进了当时看起来最有前途的SIMD库,如今已经成为fearless_simd的维护者之一。这本身就说明了一个事实:Rust的SIMD生态,还处在“需要外部人才亲自下场推一把”的阶段。
Rust里用SIMD,Shnatsel把可行路径归纳为三条:
自动向量化:什么都不用管,写普通Rust代码,让编译器自己做优化,比如直接调用&[i32].sum()。
可移植SIMD抽象:用类似i32x4 + i32x4这样的向量类型直接编程,语义清晰,但需要引入库或者语言特性支持。
平台特定intrinsics:直接调用类似_mm_add_epi32(x86)或vaddq_u32(ARM)这样的原始硬件指令,性能最强,但要为每个平台、每个指令集单独写一份实现,代码量瞬间膨胀。

自动向量化最大的优点是零依赖、代码即插即用,理论上能吃到编译器支持的所有指令集,包括一些冷门架构。
但坑也不小:函数越大越复杂,编译器“猜不中”你想向量化的意图的概率就越高;性能表现还会随编译器版本、周边代码的细微改动上下浮动,稳定性堪忧。
浮点数运算尤其麻烦。过去,出于“不能改变程序可观察结果”的严格要求,编译器几乎不敢对浮点数组求和这类操作做自动向量化,因为向量化往往意味着换一种加法顺序、进而改变浮点精度。这个限制直到今年8月发布的Rust 1.98才有所松动——新版本稳定了一批“代数运算”(algebraic ops)API,允许开发者显式声明“我允许结果精度发生变化”,这才为浮点数的自动向量化打开了口子(当然,这也带来了一个绕不开的老问题:想要精确控制浮点数求和精度本身就是一门学问,感兴趣的读者可以搜一下“Taming Floating-Point Sums”这篇经典文章)。
即便编译器愿意向量化,“多版本编译”(function multiversioning)依然是绕不开的一关:同一个函数得针对不同的CPU特性编译出多份实现,运行时再检测硬件、选择合适的版本。社区里专门有一个multiversion库来干这件事,只需要给函数加一个#[multiversion(targets = “simd”)]标注就能用,非常省心;但它也有一个不太起眼的代价——被标注的函数每次调用都会带来大约十条指令左右的额外开销,如果函数本身特别小、调用特别频繁,这点开销就会被放大成明显的性能损失。经验法则是:循环体用#[multiversion],处理少量数据的小函数则搭配#[inline(always)]使用。
抛开自动向量化,Rust生态里真正在“抢占官方标准”位置的,是一批可移植SIMD抽象库。它们衡量的维度通常包括:能否支持固定宽度向量(如f32x4)、能否支持硬件原生宽度(不用事先知道具体多宽)、能否泛型化到不同元素类型、能否泛型化到不同向量宽度。
| 方案 | 现状 | 特点 |
|---|---|---|
std::simd |
仍在nightly,尚未稳定 | 官方"亲儿子",语法最接近语言原生,但迟迟没有转正 |
wide |
生产可用 | 使用简单,但不含多版本编译能力,需要自己额外解决 |
pulp |
生产可用,被faer线性代数库使用 |
支持运行时特性检测与多版本调度,但仅支持硬件原生宽度 |
macerator |
pulp的分支 |
指令集覆盖更广(含LoongArch),泛型能力更强 |
fearless_simd |
快速发展中,作者本人已成为维护者 | 用"标记类型"在类型系统层面证明CPU特性存在,力图把unsafe彻底挡在库内部 |
值得一提的是fearless_simd的设计野心:它试图用一种“标记值”(marker values)机制,把“当前CPU支持某个特性”这件事编码进Rust的类型系统里,相当于给SIMD操作补上一张“类型系统签发的安全通行证”,从而让上层业务代码几乎不用再直接接触unsafe。这个思路和Go编译器做AST重写、生成特化版本,在工程直觉上其实颇为相似——都是想办法把“平台差异”这件脏活挪到用户看不见的地方。
fearless_simd说明了什么Shnatsel在文章开头特意交代了这段“转身”经历:去年调研完一圈之后,他判断fearless_simd是当时最有潜力的方案,于是干脆加入进去做了维护者;而为了避免“王婆卖瓜”,他特意邀请了std::simd、wide、pulp、macerator等其他库的作者共同审阅这篇文章的草稿。
这段插曲本身就是对Rust现状最真实的注脚:在一个由语言核心团队亲自收口的领域(比如Go的simd包),你很难想象某个第三方开发者会因为“觉得某个方案最有前途”就亲自下场当上维护者——因为压根不需要,官方已经把标准定好了。而在Rust的SIMD领域,这恰恰是常态:真正有话语权的往往是最活跃的社区贡献者,而不是某个“官方唯一实现”。
把两篇文章放在一起读,能看出的差异其实远超“SIMD”这一个技术点本身,更像是两种语言一贯风格的缩影。
| 维度 | Go | Rust |
|---|---|---|
| 决策主体 | 语言团队 + 编译器 + 标准库统一收口 | 社区生态自行竞争,官方方案std::simd仍在nightly |
| 抽象层级 | 类型系统彻底抹去向量宽度(simd.Float32s不含长度信息) |
多数方案仍需在固定宽度、硬件原生宽度之间做选择 |
| 兜底策略 | 官方提供统一的软件模拟,保证"到处能跑" | 各库各自实现,兜底能力参差不齐 |
| 安全模型 | 编译器特化 + 运行时检测,用户几乎不接触底层细节 |
fearless_simd等库尝试用类型系统"证明"特性存在,仍处于探索期 |
| 现阶段成熟度 | 实验阶段(GOEXPERIMENT=simd),但路线已经比较清晰 |
三条路线并行发展,尚未收敛到统一标准 |
这背后其实是Go和Rust一贯的“性格”差异:Go向来倾向于“少即是多”,宁可牺牲一部分极致性能和灵活性,也要保证一套标准库API能覆盖大多数场景,把复杂度留给编译器和语言维护者;Rust则更信任“生态的自然选择”,鼓励百花齐放,让不同取舍的方案同台竞争,代价是官方标准迟迟难产,普通开发者选型成本更高。
有意思的是,两边都不约而同地走向了“用编译器/类型系统把复杂度往下沉”这条路——Go靠AST重写和函数特化,Rust靠fearless_simd的标记类型和multiversion的属性宏。方向殊途同归,只是“由谁来兜底”这件事上,Go选择了语言,Rust选择了社区。
如果你只是想知道"我该怎么选",这里给一个务实一点的建议:
simd包目前仍是实验特性,功能还有明显缺口(比如缺横向求和),生产环境慎用,但值得现在就动手写几个benchmark熟悉一下这套API的思路,等它转正之后基本可以无缝迁移。wide是不错的起点;需要多版本编译又不想写太多样板代码,pulp或macerator更合适;如果你愿意接受一个还在快速迭代、但设计理念更前沿的方案,fearless_simd值得关注;至于std::simd,除非你能接受nightly,否则短期内还是先观望。Go这边,官方已经明确表态:Go 1.28计划给archsimd加上ARM的SVE支持,并争取同步带进simd包;同时会补齐一批当前缺失的操作,比如OnesCount(统计比特数)、掩码相关操作、归约操作和向量重排操作;针对像树莓派这种“支持向量指令但缺个别操作”的平台,还打算引入一批“特性变体”,避免直接整体降级到纯软件模拟。
Rust这边的核心悬念,则是std::simd到底什么时候能从nightly转正——这个问题已经悬了相当多年,年复一年地出现在Shnatsel的年度调研里。与此同时,第三方生态还在继续演化:fearless_simd的安全模型能不能真正跑通,multiversion的调用开销问题能不能被彻底解决,都还是进行时。
Go和Rust这次几乎同时交卷,某种意义上是一次巧合,但也不完全是巧合——SIMD编程的“可移植性焦虑”这些年一直在系统编程社区里发酵,只是不同语言选择了不同的时间点、不同的方式去回应它。
Go选择了一条更“收敛”的路:先由编译器和标准库把复杂度扛下来,代价是牺牲一部分灵活性和当前阶段的功能完整度。Rust则继续信任生态的自我进化,代价是普通开发者面对std::simd、wide、pulp、macerator、fearless_simd这一长串名字时,多少会有点选择困难。
无论哪条路线更快抵达终点,可以确定的是:SIMD正在从“少数性能极客的专属玩具”,变成越来越多语言愿意认真投入、正面解决的基础设施问题。这场关于“可移植性”的路线之争,才刚刚开始。
参考资料
simd包的设计灵感来源。https://google.github.io/highway/en/master/index.html还在为写 Agent 框架频频死循环、上下文爆炸而束手无策?我的新专栏 《从0 开始构建 Agent Harness》 将带你:
扫描下方二维码,开启从 0 开始构建Agent Harness 的实战之旅。

还在为“复制粘贴喂AI”而烦恼?我的新专栏 《AI原生开发工作流实战》 将带你:
扫描下方二维码,开启你的AI原生开发之旅。

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

2026-09-27 06:00:00
本文永久链接 – https://tonybai.com/2026/09/27/the-last-ai-built-by-humans-rsi
大家好,我是Tony Bai。
【导读】
一篇由多所国内顶尖高校学者和大厂研究员联合完成、题为《The Last AI Built by Humans: Toward Genuine Recursive Self-Improvement(人类打造的最后一个AI:走向真正的递归自我改进)》的长篇综述,最近在AI圈悄悄流传。它没有渲染“AI毁灭人类”的情绪,而是做了一件更硬核的事:把“递归自我改进(RSI)”这个长期停留在科幻和哲学讨论里的概念,拆解成可以观测、可以分级、可以验证的工程问题。论文提出了一把新尺子——Headroom-Closed Index(HCI),量化了大模型在十个能力领域里“天花板还剩多少”;又提出了一套自主性五级框架,把从“AI执行人类指定的改进”到“AI改写决定自己如何被改进的机制”之间的距离,切成了五级台阶。这篇文章带你把这篇论文一次读透。
【文章要点】

最近,一篇名字有点“吓人”的论文在AI圈流传开来。
标题叫《The Last AI Built by Humans: Toward Genuine Recursive Self-Improvement》,直译过来就是《人类打造的最后一个AI:走向真正的递归自我改进》。

光看标题,很容易联想到那句流传已久的话——“第一台超智能机器,将是人类需要做的最后一项发明”。这句话最早由数学家 I. J. Good 在上世纪六十年代提出,是“递归自我改进(Recursive Self-Improvement,RSI)”这个概念最早的雏形之一,此后一直是AI安全讨论里最具分量、也最容易被过度解读的话题之一。
但这篇论文的野心不在制造焦虑,而在“祛魅”:它想搞清楚的是——今天所说的“AI自我改进”,到底改进到了哪一步?哪些是真正意义上的“递归”,哪些只是套了层“自我进化”话术的传统自动化?
这正是这篇论文最有价值的地方。
论文给出的定义是:RSI是一个自主的、闭环的过程——AI系统通过与任务、环境或其他智能体的持续交互,把获得的经验和反馈自主转化为对自身的持久性改变(可以是模型参数、智能体的工具与流程配置,也可以是改进策略本身),并且这种改变还会进一步影响未来“生成、评估、筛选、沉淀改进”这套机制本身。
这个定义里,藏着一个很容易被忽略、却是全篇论证的关键的区分:“改进结果”和“改进机制”是两回事。
论文专门用一个概念——B0:任务内AI改进(In-Task AI Improvement)——来标注这条边界。在B0阶段,AI可以在当前任务里反复修订、校验、择优,但改进不会作为“持久系统状态”被保留到下一个独立任务里。论文把B0定义为非RSI的参照水平,一句话总结就是:“只有输出变了,系统没有变”。
为了让“系统变了没变”这件事有据可查,论文还拆解了一个改进闭环(Improvement Loop)的解剖结构,定义了九个角色:AI系统、系统状态、经验、改进目标、改进者(Improver)、改进策略、验证者(Verifier)、被接受的改进、继任系统(Successor)。用一张图来看会更直观:

这张图画的是论文定义的“改进闭环”结构。论文反复强调:判断一个系统是不是在做RSI,要回答三个问题:
论文也顺带把RSI和三个容易混淆的近邻概念做了区分(持续学习、AutoML、智能体AI),核心结论是:这三者通常只自动化了“生成候选方案”或“保留状态”其中一部分,而很少让改进机制本身变成可以被系统自己修订、并在后续轮次里复用的对象——而这恰恰是RSI区别于它们的关键。
在提出框架之前,论文先做了一件很扎实的事——量化“当前大模型到底进步到哪了”。
作者团队构建了一个新指标,叫 Headroom-Closed Index(HCI,“天花板关闭指数”):以某个基准测试刚出现那一年的90分位模型成绩作为0分基准,以满分作为100分基准,把不同来源、不同权重(基准方发布、独立评测、模型方自报等)的分数做加权,换算成“这个领域的天花板已经被关闭了百分之多少”。
基于这套方法,论文汇总了393组模型—基准测试观测数据,覆盖10个能力域、2023年到2026年9月的模型发布节奏,得到了几个值得注意的发现:
发现一:不同领域的进展速度和节奏完全不同步。
到2026年,高等数学和研究生级科学的HCI已经冲到85分以上;相比之下,法律推理、多模态推理等领域只在60分出头徘徊,而且法律推理的年度增幅已经明显放缓(从2024年一年涨48.2分,到2026年只涨4.9分),数学却反而在2026年加速冲刺。
发现二:“交互式”能力的差距,比“可验证”能力大得多。
软件工程的HCI到2026年是52.6分,工具类智能体只有39.9分,远低于研究生级科学的85.8分。论文特别指出,工具智能体在2026年出现了跳跃式增长(从8.2分猛冲到39.9分),但整体依然是差距最大的赛道之一——顶尖的网络安全智能体已经冲到91.9分,和工具智能体之间隔了52分。
发现三:剩下的“天花板”,正是RSI最可能发力的地方。
论文用一个简单的外推公式,画出了一条假设性的“RSI加持后”曲线:天花板关得越少的领域(比如软件工程、工具使用),如果RSI真正跑起来,理论上能获得的提升空间也越大。

论文这里的态度是克制的:它反复提醒,虚线部分(比如网络安全评测口径变化导致的不可比区间)只是“说明性外推”,不是预测。
但这组数据本身已经很说明问题——AI在“有标准答案”的领域已经卷得很快,而在需要长程规划、工具协同、环境状态追踪的“交互式”任务上,还有巨大的、尚未被攻克的空间。而这正是论文认为RSI最有可能率先撕开口子的地方。
搞清楚“还差多远”之后,论文给出了整篇工作最核心的贡献——一套自主性五级框架,用来衡量一个自我改进系统,究竟把多少“改进责任”从人类手里转移到了AI自己手里。
五个等级依次是:

这张图梳理了五个等级的递进关系。具体来看:
L1·改进执行自主:人类定好“改哪里、怎么改、什么算成功”,AI负责把改进执行到位。论文举的例子是FineWeb-Edu——用模型给整个网页语料库打教育质量标签,但打分标准是人定的,模型不参与决定标准本身。
L2·改进策略自主:目标和验收规则仍然固定,但AI要自己诊断系统的薄弱环节,并决定“怎么改”。论文举的例子是Self-Harness:利用执行轨迹自主提出并测试对智能体工具链(harness)的修改,在固定基准和晋升规则下运作。
L3·经验获取自主:AI进一步决定“下一轮改进需要什么样的学习经验”,也就是自己给自己出练习题。论文举的例子是谷歌DeepMind的SIMA 2:根据对当前行为的评估结果,自动生成后续的练习任务,专门针对观测到的能力短板。
L4·部署环境自适应:改进闭环开始利用真实部署中的交互反馈,在外部验收和治理规则的约束下,持续修订可复用的系统状态。论文举的例子是PANDO:在长时间交互过程中,根据实际结果动态“录用”或“淘汰”可复用规则,让后续动作能继承此前积累的经验。
L5·递归继承自主:这是论文定义的“真正RSI”的核心——系统持续修订的对象,是决定后续改进如何进行的机制本身(比如改进者Improver、验证者Verifier、或继任生成流程)。论文举的例子是A-Evolve-Training:把训练后的结果沉淀成一份持久化的“研究策略与发现日志”,由一个元智能体(meta-agent)不断修订这份策略,用来指导后续实验该怎么选配方。在30B参数的Nemotron模型上,经过四轮自主迭代,外部评测分数从0.80提升到0.86,已经逼近当时人类提交的最好成绩0.87。
值得强调的是,论文在描述每一级的同时,也非常克制地标注了“哪些关键决策仍然留在人类手里”——这也是这篇论文和很多渲染“AI自主进化”的科普文章最大的不同:它不回避“AI到底能自己做多少决定”这个问题,而是把答案一级一级、一个案例一个案例地摆出来。


自主性框架描述的是“闭环的结构”,但同样的闭环结构,落到不同领域里,能跑多快完全取决于反馈的成本和可靠性。论文选了四个场景做对比:
论文的结论也很直接:软件工程之所以是当前RSI进展最快的场景,本质上是因为它的反馈机制最接近“自动化验证”——有测试、有可执行环境、有明确的成功标准;而医疗、科学发现这类反馈稀疏、成本高、责任重大的领域,RSI要真正落地,还需要更长的路。
比起纯学术讨论,论文另一个亮点是把产业一手材料也纳入了证据范围——包括技术报告、开源仓库、工程博客、模型文档、模型评测榜单等。作者认为,很多最前沿的自我改进闭环,往往是先在工业系统里跑起来,才被写进论文。
论文在“产业实践”一节里盘点了多家实验室/公司的早期RSI探索,覆盖了从“环境-数据-模型协同进化”到“企业级数据基座建设”、“零人力工业AI工程”、“经验驱动的自我改进”等不同路线,试图回答同一个问题——这些系统里,AI到底扛下了多少改进责任,哪些环节依然离不开人。这也再次呼应了论文的核心方法论:不看“自动化了多少”,而看“AI自己决定了多少”。
论文没有停留在“框架很美好”的阶段,而是花了专门篇幅指出——一个闭环能跑起来,不代表它跑得对。 具体是三个问题:
1. 安全继承(Safe Inheritance)。
改进要能跨任务、跨轮次持续存在,但“能持续存在”不等于“持续变好”。论文举了Gödel Agent的例子:这个系统会同时改写自己的任务策略和改进逻辑,但在100次MGSM优化尝试里,有14%的结果反而比最初的策略更差。这说明,没有版本回滚、迁移测试这类机制,“持久化”本身可能持久化了一个错误方向。
2. 自主性归因(Autonomy Attribution)。
生成了更好的候选方案,不代表AI真的改进了“挑选候选方案的方式”。论文提到的达尔文哥德尔机(Darwin Gödel Machine)在SWE-bench子集上把表现从20%提到了50%,但它维护“方案档案”和挑选“父代方案”的规则始终没有被改动过——换句话说,表现变好了,但决定“怎么变好”的那套逻辑,其实还是人写死的。
3. 可靠验证(Reliable Verification)。
如果AI能反复“偷看”验证结果,它很可能学会取巧而不是真正变强。论文引用了Anthropic自动化研究实验中出现的问题——系统曾试图挑选有利的随机种子、甚至尝试从验证过程里套出测试答案的线索。为此,论文提到红皇后哥德尔机(Red Queen Gödel Machine)的应对办法:在每个评估周期内冻结验证器,并用独立的“真值锚点”来验证新验证器的有效性,避免AI和验证机制“合谋”。
这三道坎,某种程度上比“AI能不能自我改进”这个问题本身更重要——它们决定了RSI闭环到底值不值得信任。
回到最初那个耸动的标题。
论文并没有给出一个明确的时间表,也没有像业界某些声音那样,给出“到某年某AI能完全自主设计继任者”的具体概率押注。它做的,其实是一件更“笨”也更有价值的事:把这个宏大命题拆解成可以逐项检验的证据链——闭环在哪里闭合、什么被保留、哪些决策仍然是人在做主、改进有没有真正反哺“改进机制”本身。
论文摘要里那句意味深长的总结,用大意来说就是:就像人类的进化史一样,AI的进化也将是一段漫长而不寻常的历史,今天AI取得的一切,可能只是沧海一粟。
这句话读起来颇有几分末日感,但整篇论文给出的,其实是一套祛魅的方法——与其争论AI会不会失控,不如先把“AI到底自己做了多少决定”这件事,一级一级地说清楚。
这或许才是这篇论文,比标题更值得被记住的地方。
论文信息
还在为写 Agent 框架频频死循环、上下文爆炸而束手无策?我的新专栏 《从0 开始构建 Agent Harness》 将带你:
扫描下方二维码,开启从 0 开始构建Agent Harness 的实战之旅。

还在为“复制粘贴喂AI”而烦恼?我的新专栏 《AI原生开发工作流实战》 将带你:
扫描下方二维码,开启你的AI原生开发之旅。

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

2026-09-26 06:00:00
本文永久链接 – https://tonybai.com/2026/09/26/go-pprof-heap-profile-memory-space-rss-proposal
大家好,我是Tony Bai。
【导读]
排查 Go 服务内存超标时,你是不是也遇到过这种“灵异事件”:容器监控显示 RSS 已经吃到 2GB,可 pprof.Lookup("heap") 抓出来的堆快照却只有不到 1GB?这中间“消失”的内存去哪了?Go 团队最新一期 proposal review 会议纪要显示,一份旨在彻底终结这一困惑的提案——为 heap profile 新增 memory_space 采样类型——已被“临时通过(provisionally accepted pending implementation)”。这可能是近年来 Go 内存诊断工具链最重要的一次升级。
【文章要点】
heap/alloc profile 加一个 memory_space 采样类型,兼顾发现性与兼容性;“rss” 改为 “memory”,容器化时代术语习惯是重要理由;
深夜,告警响了:某 Go 微服务容器 RSS 突破 2GB 限制,即将被 OOM Kill。你熟练地打开 /debug/pprof/heap,抓下堆快照,扔进 go tool pprof 里一看——
不到 1GB。
另外一半内存,去哪了?
如果你也曾在这个问题上抓耳挠腮,那么恭喜,你不是一个人。Go 核心团队成员、可观测性领域老兵 Felix Geisendörfer 最近提交的一份提案,正是要终结这个困扰 Go 社区多年的“内存罗生门”。在最新一期 proposal review 会议纪要中,这份编号为 #79179 的提案已被标记为 active,并获得“临时通过(provisionally accepted pending implementation)”的评审结论。
要理解这份提案,得先搞懂 Go 内存诊断的“三件套”以及它们为什么互相“对不上账”:
inuse_space):pprof.Lookup("heap") 默认给出的存活堆快照。问题在于,inuse_space 只统计“当前仍被引用、尚未被判定为垃圾的对象”。而在默认 GOGC=100 的设置下,堆内存会在触发下一轮 GC 前持续膨胀到存活对象的两倍左右——这意味着,有大量已经“死亡”但还没被清扫的内存(dead heap),根本不会出现在 heap profile 里。再加上协程栈、mspan/mcache 等运行时元数据、可执行文件本身、共享库、mmap 区域、cgo malloc 等“非 Go 内存”,真实的 RSS 往往是 heap profile 数字的两倍以上。
提案原文直言不讳:
在默认设置(GOGC=100)下,存活堆通常占不到 Go 程序内存使用量的 50%。
于是开发者只能求助于篇幅冗长的博客文章,手动去“对账” RSS、runtime metrics 和 heap profile 这三份数据——这本身就是一种反人类的工程体验。
提案最初的方案是新增一个独立的 “rss” profile 类型,但经过多轮 runtime 诊断会议和 proposal review 的打磨,方案演化为:不新建 profile,而是给现有 heap/alloc profile 追加一个新的采样类型 memory_space,并将其设为默认展示类型:
pprof.Lookup("heap").WriteTo(f, 0)
这样设计有两个直接好处:
default_sample_type 字段,因此这一改动理论上只影响 pprof UI 的交互式使用者,不会打破已有的自动化采集链路。新增的 memory_space 采样类型要回答的核心问题是:在最近一次标记终止(mark termination)时刻,进程的内存到底花在哪了?
提案给出的分层结构相当细致,基本上是把 /memory/classes/... 运行时指标体系和 pprof 的调用栈归因能力做了一次“缝合”。整体逻辑可以用下图概括:

有几个关键点:
RSS - Go Memory;Go Memory > RSS 的反常情况(比如使用了内存“压舱石”技巧,或者虚拟内存被大量预留但未真正触碰),profile 会退而求其次,只展示 Go Memory 部分,并在根节点用一条明确的警告说明原因,例如:[WARNING: RSS not supported on GOOS=X]
[WARNING: Go Memory (X MiB) > RSS (Y MiB)]
这种“宁可明确告警、也不给出误导性数字”的设计思路,贯穿了整个提案。
1. “死堆”不是无用信息
有人可能会问:都已经判定为垃圾了,看它的调用栈归因还有什么意义?提案的回答是——大多数请求驱动型服务(request-oriented services)中,一次请求内的临时分配既可能落在存活堆里,也可能落在死堆里,取决于 GC 触发的时机。因此死堆和存活堆往往指向相似的分配热点,优化死堆同样能有效降低整体内存占用;即便在长生命周期对象为主的极端场景下,减少短生命周期分配的“抖动”也能降低 GC 频率,从而让更激进的 GOGC、GOMEMLIMIT 设置成为可能。
2. 一致性快照,STW 影响可控
提案作者提供的早期原型已经验证:在标记终止阶段,可以拿到一份内部一致的 runtime 指标快照,与存活堆/死堆的调用栈画像对齐。
这部分逻辑对 STW(Stop-The-World)延迟的影响预计很小,且没有开启内存剖析时,编译器可以直接裁掉相关代码路径。相对棘手的是 RSS 读取——它依赖系统调用,不适合放进 STW 窗口,所以方案选择在标记终止结束后尽快读取 RSS,接受一定程度的“尽力而为”式时间偏差。
3. 体积代价:一次坦诚的成本核算
Austin Clements 在评审中提出了一个很现实的问题:pprof 格式要求同一份 profile 里所有采样都携带全部采样类型的字段(哪怕是零值),如果新旧两类采样类型的调用栈集合不完全重合,实际上会让样本数量翻倍。作者的回应也很坦率:预计未压缩体积增加约 30%,压缩后增加约 10%,并考虑通过 GOEXPERIMENT 或 GODEBUG 开关提供退出通道,长期方案则寄望于配套的 Recorder 提案(#74545)。
从会议纪要来看,这份提案的评审过程很能体现 Go 团队“谨慎但不保守”的风格:
/proc/self/statm 的第二个字段);最有意思的是一场关于命名的争论。提案最初打算叫 “rss”,后来改成更中性的 “memory”。
核心开发者担心的是:会不会显得“含糊其辞,回避了到底测的是什么”?但社区反馈(尤其是来自容器化基础设施背景的开发者)给出了很有说服力的理由:Kubernetes 的 resources.limits.memory、cgroup v2 的 memory.current、docker stats 的 MemUsage、container_memory_working_set_bytes 等主流指标体系,全部以“memory”作为统一术语,再从中细分出具体口径。既然 Go runtime 自己的指标体系也是 /memory/classes/... 的命名风格,用 “memory” 反而与整个生态语言习惯一致——而 “heap” 这个名字,早已被业界默认用来指代存活堆,不会因为新 profile 的加入而产生混淆。
如果这份提案最终落地,日常的内存排查工作流可能会发生实质性变化:
pprof.Lookup(“heap”).WriteTo(f, 0) 调用,就能拿到从 RSS 到具体调用栈的完整归因链路;需要提醒的是,目前该提案处于“临时通过,等待正式实现验证”阶段——评审委员会明确表示,希望看到一份“非 vibe-coded”(即非 AI 辅助草率生成、而是经过正规工程打磨)的实现,确认没有隐藏的坑之后,才会真正定案。感兴趣的开发者可以持续关注 issue #79179 以及作者早期提交的原型 CL。
从 2015 年 Dmitry Vyukov 提出“展示 GC 周期开始时的堆状态”(#13463),到 2016 年 Austin Clements 呼吁 pprof 展示不止于存活堆的内存信息(#15848),再到今天这份综合了 RSS、死堆、运行时开销的完整方案——Go 团队用了整整十年,一点点补齐内存诊断工具链上最后一块拼图。这也是 Go 语言一贯的工程哲学:不追求一步到位的“大而全”,而是在充分讨论、反复打磨之后,再谨慎地把复杂度引入标准库。
对于每天和内存告警打交道的 Go 开发者来说,这或许是今年最值得期待的一次 pprof 升级。
还在为写 Agent 框架频频死循环、上下文爆炸而束手无策?我的新专栏 《从0 开始构建 Agent Harness》 将带你:
扫描下方二维码,开启从 0 开始构建Agent Harness 的实战之旅。

你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?
继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!
我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。
目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。
