MoreRSS

site iconTonyBai | 白明修改

重复
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

TonyBai | 白明的 RSS 预览

AI时代,如何保持住那份编写代码的乐趣

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 悄悄抽走:把代码编写全权甩给大模型,不仅会导致人类自身技能退化、陷入“AI 倦怠”,更会让代码库退化为人类难以看懂、无法排障的陌生荒原;
  • 底线思维:代码必须亲手写:大模型更擅长生成“只有它自己能维护”的代码,为了保持对系统的长久掌控力与解决问题的嗅觉,核心实现绝不能假手于机器;
  • 核心大招:“一起规划,但你来敲代码”:让 LLM 研究仓库、梳理任务脉络并提示技术坑点,但真正的敲代码环节由人类亲自完成,在极简心流中享受纯粹的创造乐趣;
  • 引入 GAN 式自动化审稿流水线:坚持“未经自动化挑刺过滤的 AI 内容绝不采纳”,用专门的审查 Agent 与生成内容博弈,同时借助 AI 审稿反查自己代码中的盲区;
  • 戒断“前沿模型迷信”与 Token 焦虑:不过度绑定黑盒且额度多变的高阶商业模型,随时储备可离线推进的任务清单,做长效可持续的“有机园丁”而非用化肥狂催进度的氛围编程者;
  • 警惕 AI 车轱辘话的“精神石棉”污染:抵制缺乏真实理解的机器废话对心智的消耗,绝不把未经提炼的 AI 生成 PR 直接丢给团队,坚守技术沟通的人性与温度。


一个很多程序员都在问自己的问题

“你正在滑向AI型倦怠吗?害怕被一个几乎不会编程、毫无质量追求、但坐拥巨额Claude账号的人抢走饭碗吗?对自己项目里的代码质量,甚至‘自己写的’代码质量感到失望?”

这是Haskell核心开发者、单子驱动函数式编程框架 Rhine 的作者 turion,在Haskell官方论坛发帖的开场白。他给这篇长文起了个直白的标题——《How to keep enjoying programming in a world of LLMs》(如何在一个LLM遍地的世界里继续享受编程)。

这句开场白,说出了不少程序员这两年心里的一种隐约不安:AI确实让交付变快了,可那种“把想法亲手敲成代码”的满足感,却常常在一次次“甩给Agent”的过程中被慢慢消磨掉。

turion在帖子里特别强调,自己并不是要打一场“反AI”的嘴仗——前沿大模型背后确实存在不少值得警惕的伦理问题,但那不是这篇文章要讨论的重点,他也提前声明这篇文章百分之百是人手打字、没有借助AI代写。他想回答的是一个更具体、也更私人的问题:代码可以交给AI写,但编程的乐趣,要怎么才能不被一起拿走?

方法论拆解:把编程乐趣抢回来的6件事

turion给出的不是一句空洞的“少用AI”,而是一整套具体到可以照抄的工作法。核心逻辑很简单:把无聊的、事务性的工作甩给AI,把动脑子、有手感的部分留给自己。

底线:代码必须自己写

turion把这条摆在最前面,几乎是全文的地基。

他的判断是,如果把编写代码这件事全权交出去,代码库迟早会变成一片“只有Agent自己能活下去的LLM荒原”——人类如果放弃动手写代码,很快就会失去维护这片代码库的能力。更麻烦的是,编程技能是会退化的:只需要几周不亲自动手、事事都交给Agent,你就会发现自己已经很难再重新捡起写代码的手感。

他还补了一刀大实话:LLM生成“能跑”的代码不难,生成人能读懂、后续能维护的好代码却远没有宣传的那么厉害——它们更擅长写“只有它自己以后会继续维护”的代码。你大概率已经体验过那种绝望:面对一整个由AI生成的文件,明知道里面藏着bug,却因为这套代码逻辑对人类来说“完全陌生”,连从哪下手排查都不知道。

把LLM当“记账员”:规划(Planning)

turion提出一个很朴素的类比:计算机从诞生之初就是记账工具,AI也不例外,只是现在这个记账工具可以用自然语言对话,而不是填表格。

具体做法是,把开发者之间冗长的讨论、测试结果,丢给LLM,让它整理成可执行的todo清单,再用Markdown文件(带frontmatter)之类的形式做持久化记录——因为当前LLM的上下文窗口再大,一旦塞得太满,也会悄无声息地“丢内容”。

但规划的决策权必须攥在人手里。turion特别强调一条红线:不要让LLM做任何关键决策,而是让它来问你问题。如果你看不懂它提出的问题,说明是AI没给够上下文——或者,也可能是你已经太累了,该歇一歇了。

调研不能当甩手掌柜(Researching)

让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总结了这套工作流的四个好处:

  1. 你一直在做自己喜欢的事——如果你喜欢编程,那就继续写。
  2. 你始终清楚代码库的真实状态——再也不会被自己“氛围编程”(vibe coding)跑出来的一堆诡异代码打个措手不及,也不用回头重写AI糊出来的“屎山”。
  3. 能更早发现糟糕的方案——一个自主编码的Agent可能会顺着一个错误方向一直跑下去,而这种问题人类一眼就能识别。
  4. 手艺不会生疏——这点不言自明,你会一直是个好程序员,甚至变得更好。

当然,Agent也有真正能派上用场的场景:清理收尾、小型任务、常规重复劳动、低风险的重构。比如代码里留的FIXME、只写了3个有代表性的case却还剩7个类似的、想给某个模块重组做个benchmark、想把一个不再维护的库换掉——这些都是合适的任务;但从零设计一个复杂系统,通常不适合甩给Agent。

turion也提醒了两个容易踩的坑:

  • 如果你只写了3个典型case,就让Agent“照着葫芦画瓢”把剩下7个类似case补完,很可能说明你该做的是抽象,而不是复制粘贴——也许这本身就是一个Traversable之类的经典类型类实例。LLM在这类场景下出了名地倾向于复制大段代码,而不是识别出可以抽象的结构,人类要为更好读、更合理的代码把关。
  • 类似地,如果你依赖Agent帮你“列出所有需要改动、需要注意的坑”,某种程度上说明你的代码库本身组织得不够好,才不得不靠LLM来做这类代码考古工作。

给AI也找个“审稿人”:GAN式review cycle

turion借用了一个很形象的类比:还记得生成对抗网络(GAN)是怎么让AI生成的图像突然变得逼真起来的吗?一个模型(生成器)负责生成图片,另一个模型(判别器)负责给它打分挑毛病,两者对抗博弈,最终生成器的产出会远好于单独工作时的水平。

把这套逻辑搬到LLM辅助编程上,就是一条自动化审稿流水线:

turion给出一条相当硬的规则:任何LLM产出的东西,不经过一轮自动化审核,就不要接受,甚至不要去读它。

这不仅适用于代码本身(在那些你确实还让它生成代码的场合),也同样适用于规划文档——逐字逐句去挑一份计划里的逻辑漏洞(比如todo 2里要重构的函数,其实到todo 7才会被写出来)实在太费神,不如干脆加一个专门负责审稿的Agent来做这件事。

他还提到一个个人体验:让AI审自己写的代码,出乎意料地有用。

有时候它只是挑几个小毛病,或者非要你多写点Haddock文档注释;但也有不少次,它真的能揪出一个实实在在的bug或疏漏,让你重新聚焦在手头的todo上。他建议大家也在自己的工作里加上这道审稿工序——如果实在不想亲自看AI写的review意见,可以让它自己把那些无关紧要的问题顺手改掉。

别迷信“前沿模型”,别被token焦虑“背刺”

有人在帖子下留言:“你这是完全没把前沿模型的能力用起来”。turion的回应很干脆:没错,这恰恰是我这套方法论的必然结果。他列了几条不迷信前沿大模型的理由:

  • 环境成本:前沿模型消耗的能源相当可观,只不过由于大模型公司普遍不太透明,具体数字只能靠猜。
  • 信任问题:很难对一个“装作比你聪明得多”的东西建立起真正的信任。最终为代码负责的是你,不是你的机器——把锅甩给别人是糟糕管理者对下属才会干的事,甩给一台机器就更荒谬了。只有当你让自己真正参与到整个过程中,你才能对结果负起责任。
  • 不确定性:越花哨的模型消耗的token越多,你没法保证一次会话能不能顺利做完任务;用小模型能搞定的事,永远是更稳妥的选择。
  • 可替代性:如果你的工作流不依赖前沿模型,未来就有机会换成开放权重或开源模型,不必长期依附于某个“技术封建领主”(technofeudal lord)。

关于token耗尽这件事,turion的态度也很鲜明:不要把“token用完”理解成“自己没算计好、买少了”,而要理解成一次服务中断。厂商卖给你的是“可以使用LLM”这个承诺,一旦兑现不了,那就是他们没有履约——一个session里到底能拿到多少token,是一个不透明、可以被平台随意调整的数字,你很难提前规划。

他给出的应对之道,是把这件事类比成在没有网络的火车或飞机上工作:提前下载缓存好大文件,永远给自己留一份可以离线完成的任务清单。

落到具体做法上,就是前面反复强调的那套流程——始终保有一份规划好的todo清单,token够用时痛快地往前冲,token没了就先去做手头能离线完成的部分,等Agent恢复了,再让它回来打扫战场。

副作用预警:AI车轱辘话是“精神上的石棉”

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写代码之后,好像没那么开心了”),拆解成了具体、可操作的工作流问题。对国内的程序员和团队来说,至少有三点值得琢磨:

  1. “甩手掌柜”式使用AI,代价可能比看起来更高。短期交付速度确实会提升,但代码库的可维护性、个人的手艺、甚至团队协作中的信任感,都可能在潜移默化中被消耗掉,而这些成本往往要等出问题时才会集中显现。
  2. AI的定位,或许更适合“记账员+调研助理+审稿人”,而不是“主笔”。把决策权和最终的“手感”留给人类,把重复性、事务性的活交给AI,可能是目前看来兼顾效率和乐趣的一种折中方案——但正如评论区所争论的,在核心库维护、复杂系统设计这类容错率极低的场景下,这套方案的边界在哪里,仍然值得每个团队自己摸索。
  3. “token焦虑”背后,其实是一个更严肃的议题:程序员的工作流,正在多大程度上被平台的定价和额度策略所绑架?这不只是turion一个人的困惑,而是整个行业在快速拥抱AI编程工具时,都需要正视的一个结构性问题。

这场发生在Haskell论坛里的讨论,或许不会给出一个所有人都认可的标准答案,但它至少提出了一个值得每个程序员认真回答的问题:当AI可以替你写代码的时候,你到底还想不想自己写?

参考资料:How to keep enjoying programming in a world of LLMs,Haskell Community Discourse论坛,作者turion及回帖网友,发布于2026年9月


还在为写 Agent 框架频频死循环、上下文爆炸而束手无策?我的新专栏 《从0 开始构建 Agent Harness》 将带你:

  • 抛弃臃肿框架,回归“驾驭工程 (Harness Engineering)”的第一性原理
  • 用 Go 语言手写 ReAct 循环、并发拦截与上下文压缩引擎等,复刻极简OpenClaw
  • 构建坚不可摧的 Safety Middleware 与飞书人工审批防线
  • 在底层实现 Token 成本审计、链路追踪与自动化跑分评估
  • 从“调包侠”进化为掌控大模型边界的“AI 操作系统架构师”

扫描下方二维码,开启从 0 开始构建Agent Harness 的实战之旅。


还在为“复制粘贴喂AI”而烦恼?我的新专栏 《AI原生开发工作流实战》 将带你:

  • 告别低效,重塑开发范式
  • 驾驭AI Agent(Claude Code),实现工作流自动化
  • 从“AI使用者”进化为规范驱动开发的“工作流指挥家”

扫描下方二维码,开启你的AI原生开发之旅。


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

Tailscale 官方揭秘提速方案:一场关于「少拷贝、多队列、缓存」的 Go 语言网络性能优化实战

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 语言网络编程实践的启发。

【文章要点】

  • Tailscale 是 Go 团队出走后创立的公司,客户端与控制面几乎全部用 Go 编写,是观察 Go 在网络基础设施领域落地效果的绝佳样本;
  • 本次提速主要包含四项技术:零拷贝式小包内存管理、多队列并行流水线、writev 向量化系统调用、netmap 冷启动缓存;
  • 小包内存优化带来约 5% 的速度提升,代价极低,却直接惠及所有 Linux / Android 设备;
  • 多队列架构专门解决了子网路由器、应用连接器、出口节点这类“多连接共享单一有序流水线”场景下的吞吐瓶颈;
  • 网络地图缓存在弱网条件下,能让“热启动”到数据面可用的速度比“冷启动”快 10 到 100 倍;
  • Tailscale 还在筹划一套原生的网络性能诊断与监控工具,因为现有工具普遍不懂 DERP、直连、Peer Relay 这些 Tailscale 特有概念。


Tailscale 和 Go:一段“分手后创业成功”的故事

如果你关注 Go 语言生态,大概率听说过 Tailscale 的名字。它的几位核心创始人和早期工程师,曾是 Go 团队在 Google 内部的成员,离开后创立了 Tailscale,用 Go 语言重新实现了一套基于 WireGuard 的零配置组网方案。可以说,Tailscale 从诞生第一天起,就是 Go 语言在真实世界高性能网络基础设施中的一块“活广告牌”。

Tailscale 的核心竞争力之一是 NAT 穿透——即便在各种“不友好”的网络环境下,也能帮设备找到彼此的直连路径。但光“连得上”还不够,“连得快”同样重要。这也是本文要聊的主题:数据平面(data plane)的性能优化。

按照官方博客的说法,过去几年 Tailscale 在这条路上已经达成不少里程碑:

  • 提升 Linux 设备的 TCP 吞吐量;
  • 对 wireguard-go 做底层改造,在裸机上突破 10Gb/s;
  • 利用分段卸载(segmentation offload)技术,把基于 UDP 的应用(比如 QUIC)吞吐量提升 4 倍以上;
  • 推出 Tailscale Peer Relays,在复杂网络条件(尤其是跨国网络)下改善连接质量。

这些积累,让 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 在博客中这样总结这项改动的意义:

这意味着更低的延迟,本质上是从数据在网卡上被读取到的那一刻,到被发送给操作系统的那一刻之间,处理速度变得更快了。

优化三:writev,让内核少搬一次家

第三项优化听起来更“底层”,也更 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 本身不会看到任何流量内容,这一点和平时并无区别。

当然,这项功能也有边界条件:

  • 只有设备此前至少成功连接过一次控制面、拿到过网络地图,缓存才能生效;
  • 设备需要有持久化磁盘空间来存储缓存;
  • 对于超大规模 tailnet,更新缓存可能带来较多磁盘写入,需要权衡;
  • 对使用 SD 卡等对写入寿命敏感的存储设备,可能不建议默认开启。

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 团队坦诚地抛出了一个行业性的痛点:性能问题依然很难诊断和测试。他们列出了当前主流性能测试工具存在的几个共性缺陷:

  • 分布式测试的“税”:大多数性能工具是点对点的,要求在每个端点上都装一遍;
  • 工作流僵化:很容易跑错测试、拿到错误结果,转而去追查一个根本不存在的问题;
  • 协议支持滞后:很多工具还不支持 QUIC、HTTP/3 这类较新的协议;
  • 缺乏 Tailscale 原生感知:通用工具无法判断一条连接走的是 DERP 中继还是点对点直连,也不知道 Peer Relay 是否能帮上忙,更看不到连接路径随时间的变化。

因此,Tailscale 正在探索一套面向自身架构的原生监控与测试工具,并公开征集用户反馈,帮助定义这套工具的方向。

小结:一次教科书式的系统工程优化

回看这篇博客,会发现 Tailscale 这一次并没有讲什么“颠覆式”的新概念,恰恰相反,四项优化几乎都是网络系统编程里的“经典动作”:减少不必要的内存拷贝、用多队列 / 多核并行替代单线程瓶颈、用向量化系统调用减少内核态往返、用本地缓存缩短启动延迟。

真正值得学习的,是这套“组合拳”背后的工程方法论:先解决内存问题,腾出空间,再用腾出来的资源去解决并发问题;先看清瓶颈发生在哪个具体场景(子网路由器 vs 应用连接器 vs 出口节点),再对症设计架构;在做数据面优化的同时,也没有忽视“看不见摸不着”但同样重要的启动延迟和可观测性问题。

对于用 Go 构建高性能网络基础设施的团队来说,这篇博客值得当作一份实战案例反复咀嚼——它告诉我们,性能优化很多时候不需要“炫技”,把最朴素的系统编程原理,扎扎实实落到每一个具体场景里,就已经足够“快”了。


参考链接

  1. Tailscale 官方博客原文:We’re making Tailscale faster https://tailscale.com/blog/making-tailscale-faster
  2. Increasing TCP throughput on Linux devices https://tailscale.com/blog/throughput-improvements
  3. Surpassing 10Gb/s on bare metal with wireguard-go https://tailscale.com/blog/more-throughput
  4. 借助分段卸载将 UDP 应用吞吐量提升 4 倍以上 https://tailscale.com/blog/quic-udp-throughput
  5. Tailscale Peer Relays(Beta) https://tailscale.com/blog/peer-relays-beta
  6. Peer Relays 如何改善跨国网络性能 https://tailscale.com/blog/peer-relays-international-networks
  7. NAT Traversal 系列:Looking ahead https://tailscale.com/blog/nat-traversal-improvements-pt3-looking-ahead
  8. 参与 Tailscale 性能测试工具需求调研 https://tailscale.typeform.com/performance

还在为写 Agent 框架频频死循环、上下文爆炸而束手无策?我的新专栏 《从0 开始构建 Agent Harness》 将带你:

  • 抛弃臃肿框架,回归“驾驭工程 (Harness Engineering)”的第一性原理
  • 用 Go 语言手写 ReAct 循环、并发拦截与上下文压缩引擎等,复刻极简OpenClaw
  • 构建坚不可摧的 Safety Middleware 与飞书人工审批防线
  • 在底层实现 Token 成本审计、链路追踪与自动化跑分评估
  • 从“调包侠”进化为掌控大模型边界的“AI 操作系统架构师”

扫描下方二维码,开启从 0 开始构建Agent Harness 的实战之旅。


你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?

  • 想写出更地道、更健壮的Go代码,却总在细节上踩坑?
  • 渴望提升软件设计能力,驾驭复杂Go项目却缺乏章法?
  • 想打造生产级的Go服务,却在工程化实践中屡屡受挫?

继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!

我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。

目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!


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

HN 热帖引爆讨论:Go 的 import 路径,为什么不该耦合 github.com?

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,评论区吵成一团:有人说全局替换五分钟搞定,有人说自己的团队为此折腾了数周。谁对?我们把双方的论据摊开来看。

【文章要点】

  • HN 辩论缘起:一篇直指“Go 的 import 路径直接写死 github.com 是严重技术债”的博文引发近 300 点热议,两派开发者围绕解耦成本与平台锁定的长期代价展开激烈攻防;
  • 双重属性的架构原罪:Go 的 import path 既是逻辑上的“模块命名空间(Identity)”,又是物理上的“代码拉取地址(Location)”,将易变的基础设施硬编码进不可变的代码中是所有痛点的根源;
  • “全局替换”与 replace 的认知盲区:单纯全局替换无法修补不可变的旧 Git tag,会导致历史构建与 git bisect 二分调试断裂;而 go.mod 的 replace 指令仅在主模块生效,对下游库使用者完全无效;
  • 自持域名的代价与期权价值:自有域名虽然伴随域名维护与过期风险,但在项目第 0 天配置是一笔成本近乎为零的高杠杆期权——不仅赋予了未来免修改代码的迁移自由,更成为企业级私有镜像、统一鉴权与供应链安全的基础控制面;
  • 标准落地与三类选型实践:系统拆解 go-import 与 go-source 的底层工作原理与 302 重定向细节,并横向对比自建静态 Nginx、自建站点模板与免运维托管服务(如 gvu)在通配路由、主版本号(/v2)及 Go 1.25+ Monorepo 子目录支持上的优劣取舍。


一篇短文,一场辩论

近日,开发者 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 到底是什么

要判断谁对,先要理解 import path 在 Go modules 里承担了什么。它同时是三样东西:

评论区里有位开发者说得很到位:其他语言也有类似问题,但 Go 特别容易“踩坑”。因为 Go 以目录为包,项目稍微大一点,就要拆目录;而拆了目录,最顺手的写法就是在源码里写下完整的 github.com/... 路径。工具链是在引导你把托管地址写进代码。

同一个字符串,既是名字,又是地址。把会变的东西,焊在了不该变的东西上。这是争论的起点。

五个交锋点:HN 评论区的攻防战

交锋一:“不就是全局替换吗?让 AI Agent 干不行吗?”

这是最高频的质疑:所有路径都指向 github.com,查找替换一遍,最多几个小时的事。有人还补刀:让编程 Agent 去做不就好了?

对当前这个仓库来说,这没错。问题出在版本历史。

评论区里,一位亲历过大规模迁移的开发者,给出了一个非常经典的推演:

  • libfoo 有 v1.2.0 和 v1.3.0 两个版本;
  • authservice 依赖 libfoo v1.2.0,apiservice 依赖 libfoo v1.3.0;
  • 现在要从 github.com 迁到内部 Git 服务器。

你把三个仓库的最新代码都改了。但 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 几乎不会消失,而自有域名一旦停止续费就没了,对个人开源开发者来说,这个概率比 GitHub 消失更高。

我们必须承认:这个风险是真实的。

用自己的域名做 import 路径,等于把身份的最终解释权,收归了这个域名。域名被抢注,别人就能对你所有模块路径“说了算”,这已经是供应链安全问题。

但也有几个反驳角度,值得放在一起看:

  • 企业场景与个人场景不同。评论区有人指出,对一家软件企业而言,如果连域名都没人续了,多半意味着这些代码也没人在意了。而个人开源作者确实要掂量这份维护责任。
  • 公共代理会缓存。Go 的公共模块代理通常会缓存已被拉取过的版本。评论区里也有人提醒:无论托管在哪里,都不该假设它明天还在,而企业更应该运行自己的 GOPROXY。
  • GitHub 也不是“永远”。仓库可以被作者删除、被组织归档、被平台策略变更影响。

所以这条争论的诚实结论是:域名不是免费的午餐,它是用“可控的运维成本”换“不可控的迁移成本”。对个人小库,未必划算;对有长期维护计划的团队和项目,通常划算。

交锋四:“这是过早优化”

有评论认为,为了未必会发生的迁移,提前配置域名,是过早优化。

这个论点在“概率”上成立,在“成本结构”上不成立。解耦的成本是不对称的:

时间点 做一次的成本
第 0 天(新项目) 一个域名+几行配置,近乎为零
第 N 天(已有大量 tag 与依赖方) 需要处理历史 tag、协调下游、破坏二分定位

这类似于“要不要从一开始就使用数据库迁移工具”:大概率你很久用不上,但一旦需要,早期没做的代价会被放大成数量级的差距。它不是过早优化,而是一份便宜的期权。

交锋五:域名,可以走得比“改路径”更远

评论区还有一个很有启发性的视角:自有域名的意义,不只是应对迁移。

一旦所有 import 都经过你的域名,你就可以在未来的任何时刻,把这个域名指向你的制品仓库或私有代理。这意味着依赖拉取有了统一入口,你可以逐步做到缓存、版本锁定、校验和检查、统一鉴权。评论区里有人认为,这将成为软件供应链安全的基本盘。

这也顺带回答了那场“该不该 vendor 依赖”的旁支争论:无论你选择 vendor、代理镜像还是直连,有一个自己控制的间接层,选择权就在你手里。

工作原理:go-import 到底做了什么

聊完辩论,看看机制本身。当你执行 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 无需额外配置;
  • 自定义域名:一条 CNAME 记录即可,TLS 证书自动签发;
  • 凭证留在本地:访问令牌和 Git 凭证只存在于开发者机器,服务端不保存 SSH key 或仓库密码;
  • monorepo:借助 Go 1.25 的子目录能力,通过 --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 ./...

三个务必注意的细节:

  1. 旧版本不能原地改名。一旦新版本的 go.mod 声明了新路径,用旧路径拉取这些新版本会报错,提示声明的路径与请求路径不一致。所以旧版本会一直留在旧路径上,依赖方升级到新版本之前不受影响,这既是保护,也是遗留问题的来源。
  2. 旧仓库要归档,不要删除。评论区有人的做法是:把旧位置设为只读归档,过几个月再收回权限,看看还有什么会坏。这比直接删除稳妥得多。
  3. 给旧路径一个明确的告别信号。在旧路径最后一个版本的 go.mod 里,用 // Deprecated: 注释标注新路径,依赖方可以通过 go list -m -u 之类的工具看到。

这次迁移会有成本,但它是最后一次。此后换 GitLab、换自建 Git、换 IP,依赖方的 import 都纹丝不动。

小结:争论的本质,是“谁来付账,何时付账”

回看整场辩论,双方其实都没错,只是站在了不同的时间点:

  • 反方说得对:对一个现有仓库,改路径本身是简单的。
  • 正方说得对:难的是历史 tag、依赖图、下游使用者,而这些成本在项目变大后会指数上升。

所以这场争论的本质,不是“要不要解耦”,而是:你愿意在第 0 天花半小时买一份便宜的期权,还是愿意在第 N 天,为一次跨团队、不可灰度的迁移买单?

Go 没有中心化的包仓库,这份自由让生态轻盈,也把一个决策交给了每个开发者:你的代码,叫什么名字?

在写下第一行 import 之前,多花一点时间想清楚它叫什么,可能是你今年 ROI 最高的一次架构决策。

你的团队现在的 import 路径,是 github.com,还是自己的域名?欢迎在评论区聊聊你的经历。


参考资料

  • Iain Cambridge,《Don’t couple your Go code to GitHub》,https://iain.rocks/blog/dont-couple-your-go-code-to-github
  • Hacker News 讨论串:news.ycombinator.com/item?id=49868404
  • GoMod Vanity URLs 官网:gomodvanityurls.com

还在为写 Agent 框架频频死循环、上下文爆炸而束手无策?我的新专栏 《从0 开始构建 Agent Harness》 将带你:

  • 抛弃臃肿框架,回归“驾驭工程 (Harness Engineering)”的第一性原理
  • 用 Go 语言手写 ReAct 循环、并发拦截与上下文压缩引擎等,复刻极简OpenClaw
  • 构建坚不可摧的 Safety Middleware 与飞书人工审批防线
  • 在底层实现 Token 成本审计、链路追踪与自动化跑分评估
  • 从“调包侠”进化为掌控大模型边界的“AI 操作系统架构师”

扫描下方二维码,开启从 0 开始构建Agent Harness 的实战之旅。


你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?

  • 想写出更地道、更健壮的Go代码,却总在细节上踩坑?
  • 渴望提升软件设计能力,驾驭复杂Go项目却缺乏章法?
  • 想打造生产级的Go服务,却在工程化实践中屡屡受挫?

继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!

我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。

目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!


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

Go与Rust同日交卷:一场关于“可移植SIMD”的路线之争

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编程的现状。两篇文章单独看都是硬核的“工程八卦”,放在一起看,却像是两个语言阵营对同一道难题交出的两份完全不同的答卷:如何既能“一次编写、到处飞快”,又不必人人都去写汇编。

【文章要点】

  • 同一道考题的两份答卷:2026 年 9 月 24 日,Go 官方发布 Go 1.27 可移植 SIMD 进展博客,Rust 社区同日发布年度 SIMD 生态全景长文,两大系统级阵营同台亮出对“无痛向量化”的解题思路;
  • Go 1.27 的自顶向下路线:全新实验性 simd 包借鉴 C++ Highway 哲学,类型层面彻底抹平向量宽度,通过编译器 AST 重写、启动期硬件特化生成以及全平台纯软件模拟兜底,追求“写一次、到处能跑且接近汇编性能”;
  • Rust 的自底向上生态:官方 std::simd 仍未走出 Nightly,社区形成“自动向量化 + 平台 Intrinsics + 第三方抽象库(wide/pulp/macerator/fearless_simd)”百花齐放的格局,甚至尝试用类型系统给 CPU 特性签发“安全通行证”;
  • 设计哲学的硬核交锋:Go 宁可牺牲部分极端调优空间也要由语言和编译器包揽复杂度,换取标准库的稳定与极简心智;Rust 信任生态自发竞争,性能上限极高但普通开发者面临繁重选型成本;
  • 硬件演进与未来风向:面对 AVX-512、ARM SVE 及 RISC-V 变长向量的硬件碎片化现实,SIMD 正在从“少数性能极客的手写汇编玩具”蜕变为现代编程语言不可或缺的核心基础设施。


引子:同一天的两篇文章

如果你不写高性能计算相关的代码,SIMD(Single Instruction Multiple Data,单指令多数据)这个词可能有点陌生。但它其实无处不在:图片解码、字符串搜索、哈希计算、向量数据库的相似度检索、甚至Go自己的垃圾回收器,背地里都在偷偷用它加速。

简单说,普通CPU指令一次只能处理一个数:把两个数加起来,占用一条指令;但SIMD指令可以让CPU一次性把8个、16个甚至64个数同时加起来,几乎是“同样的时间,多干好几倍的活”。问题是,这份“福利”长期以来只有写汇编或者调用平台专属指令集(Intrinsics)的人才能真正吃到嘴里,普通业务代码很少能沾光。

于是,“怎么用一种优雅、可移植的方式暴露SIMD能力”,就成了几乎所有系统级编程语言都要面对的共同难题。而就在这个九月,Go和Rust两个阵营几乎同时交出了各自的最新答卷——但答案却南辕北辙。

SIMD是什么,为什么最近又被反复提起

先简单铺垫一下背景。

现代CPU的算术运算单元其实非常廉价,一颗芯片里塞满了加法器、乘法器,但真正的瓶颈在“指令译码”这个环节——CPU一次只能高效地译码和调度有限数量的指令。SIMD的思路就是:既然算术单元这么多,不如一条指令喂给它一整“批”数据,而不是一个一个数地喂。

于是同样是做加法,标量(Scalar)指令一次只加一对数,SIMD向量(Vector)指令一次可以加8对、16对甚至更多,理论上能拿到数倍到数十倍的吞吐提升。

但天下没有免费的午餐,SIMD的“坑”主要来自硬件的碎片化:

  • x86平台上,SSE2、AVX、AVX2、AVX-512是逐代叠加的扩展,同一颗x86_64 CPU未必支持最新指令集;
  • ARM平台的NEON倒是64位ARM芯片标配,相对省心;
  • WebAssembly也有自己的128位SIMD扩展,但需要单独编译一份支持SIMD的二进制;
  • 更不用说RISC-V、LoongArch这些还在发展中的架构,向量宽度甚至是运行时才能确定的变量。

正是这种“同一个概念,十种实现方式”的现状,逼着Go和Rust都不得不认真面对“可移植SIMD”这道题。

Go 1.27的答案:一个“抹平差异”的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包定下的设计目标是:

  1. 覆盖大多数向量化算法所需的常见操作,但不绑定具体向量长度;
  2. 当源码操作能精确对应到硬件指令时,效率要做到接近汇编;
  3. 其余情况,尽量高效地用软件模拟补齐;
  4. 代码要足够易读——用官方原话说,“即使是LLM来写这段代码,也得写得明白”。

具体做法是:

  • 先把所有主流架构都支持的操作取“交集”,做成simd包的基础能力;
  • 交集之外但确实重要的操作(比如密码学和CRC校验用到的无进位乘法),就手写模拟实现补上;
  • 实在没法通用的高阶操作(比如“横向相加”这种依赖向量长度的原语),干脆换个思路,直接提供更上层、跟向量长度无关的语义(比如求和归约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要等到下一个版本才补上,目前示例里还得手写一个辅助函数。

编译器怎么把它变快:AST重写与特化

这一层“看不见的魔法”值得单独说一说。Go团队坦言,simd其实同时是一个包、一个内部实现包,外加编译器前端的一段AST(抽象语法树)重写逻辑。

编译器会为所有涉及simd类型的函数、变量和类型生成多份"特化副本",分别对应128位、256位、512位向量或者纯模拟这几种情况,并在函数名后面加上类似@simd256这样的后缀。

程序启动时先检测硬件实际支持的SIMD级别,之后统一走到对应的特化版本,特化函数之间互相调用不再需要额外的判断开销,甚至可以直接内联。这算是一种在“代码体积”和“运行时性能”之间的折中:把判断开销尽量往调用链上层推,但又不会推得太高,SIMD计算内部完全没有额外分支负担。

GODEBUG调试开关

为了方便开发者在没有对应硬件的情况下也能测试各种SIMD路径,Go还提供了一组GODEBUG=simd=...开关,比如强制走纯模拟(simd=0)、强制使用128/256/512位向量、甚至允许“明知道某些特性缺失也硬上”(用+前缀),方便针对Raspberry Pi、Apple Silicon的amd64模拟环境这类“能力不全”的场景做兼容性测试。

Rust的答案:一场持续了七年的“群雄逐鹿”

如果说Go的策略是“编译器亲自下场,标准库统一收口”,那么Rust给出的答案完全是另一种画风——生态百花齐放,至今没有收敛出一个“官方唯一答案”。

Sergey Davidoff(社区里更熟悉的名字是Shnatsel,Rust安全代码工作组负责人)这篇《2026年Rust SIMD生态现状》,正是延续他去年同名系列的年度盘点。有意思的是,他这次不再是纯粹的“第三方观察者”——去年写完调研之后,他一头扎进了当时看起来最有前途的SIMD库,如今已经成为fearless_simd的维护者之一。这本身就说明了一个事实:Rust的SIMD生态,还处在“需要外部人才亲自下场推一把”的阶段。

三条路线:自动向量化、可移植抽象和平台intrinsics

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)]使用。

可移植SIMD库生态盘点

抛开自动向量化,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领域,这恰恰是常态:真正有话语权的往往是最活跃的社区贡献者,而不是某个“官方唯一实现”。

正面交锋:Go vs Rust的设计哲学

把两篇文章放在一起读,能看出的差异其实远超“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选择了社区。

对开发者意味着什么

如果你只是想知道"我该怎么选",这里给一个务实一点的建议:

  • 如果你在写Go:Go 1.27的simd包目前仍是实验特性,功能还有明显缺口(比如缺横向求和),生产环境慎用,但值得现在就动手写几个benchmark熟悉一下这套API的思路,等它转正之后基本可以无缝迁移。
  • 如果你在写Rust:追求极致省心,wide是不错的起点;需要多版本编译又不想写太多样板代码,pulp或macerator更合适;如果你愿意接受一个还在快速迭代、但设计理念更前沿的方案,fearless_simd值得关注;至于std::simd,除非你能接受nightly,否则短期内还是先观望。
  • 不管用哪种语言:SIMD从来不是“无脑加速器”,向量化以后未必总是更快(比如AVX-512在部分CPU上会触发降频),务实的做法永远是先测再上。

尚未解决的问题与下一步

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正在从“少数性能极客的专属玩具”,变成越来越多语言愿意认真投入、正面解决的基础设施问题。这场关于“可移植性”的路线之争,才刚刚开始。

参考资料

  1. David Chase, Junyang Shao. Platform-independent SIMD in Go. The Go Blog, 2026-09-24. https://go.dev/blog/simd-experiment
  2. Sergey “Shnatsel” Davidoff. The state of SIMD in Rust in 2026. 2026-09-25. https://shnatsel.github.io/state-of-simd-rust-2026/
  3. Sergey “Shnatsel” Davidoff. The state of SIMD in Rust in 2025(上一年度调研). https://shnatsel.medium.com/the-state-of-simd-in-rust-in-2025-32c263e5f53d
  4. Linebender. Towards fearless SIMD, 7 years later. https://linebender.org/blog/towards-fearless-simd/
  5. Highway:Google开源的C++可移植SIMD库,Go simd包的设计灵感来源。https://google.github.io/highway/en/master/index.html

还在为写 Agent 框架频频死循环、上下文爆炸而束手无策?我的新专栏 《从0 开始构建 Agent Harness》 将带你:

  • 抛弃臃肿框架,回归“驾驭工程 (Harness Engineering)”的第一性原理
  • 用 Go 语言手写 ReAct 循环、并发拦截与上下文压缩引擎等,复刻极简OpenClaw
  • 构建坚不可摧的 Safety Middleware 与飞书人工审批防线
  • 在底层实现 Token 成本审计、链路追踪与自动化跑分评估
  • 从“调包侠”进化为掌控大模型边界的“AI 操作系统架构师”

扫描下方二维码,开启从 0 开始构建Agent Harness 的实战之旅。


还在为“复制粘贴喂AI”而烦恼?我的新专栏 《AI原生开发工作流实战》 将带你:

  • 告别低效,重塑开发范式
  • 驾驭AI Agent(Claude Code),实现工作流自动化
  • 从“AI使用者”进化为规范驱动开发的“工作流指挥家”

扫描下方二维码,开启你的AI原生开发之旅。


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

人类打造的最后一个AI?国内顶尖学者联合发文,把“AI自我进化RSI”拆成了五个等级

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》,但内容是严谨的工程分析,而非“AI末日预言”。
  • 论文提出 Headroom-Closed Index(HCI,天花板已关闭指数),用393组模型—基准测试观测数据,量化了十个能力域从2023年到2026年的进展速度差异。
  • 数学、科学等“可验证”领域进展飞快,而软件工程、工具使用、多智能体协作等“交互式”能力,进展慢得多、差距更大——这恰恰是RSI最有可能率先突破的地方。
  • 论文给出了一套自主性五级框架(L1-L5),把RSI从“AI执行任务”到“AI改写决定改进的机制本身”划出清晰边界,并给每一级配上了真实系统案例。
  • 论文把RSI放进科学发现、具身智能、软件工程、医疗四个场景里做对比,并盘点了国内外多家产业实验室的早期RSI实践。
  • 论文同时指出了RSI要落地必须跨过的三道坎:安全继承、自主性归因、可靠验证——用几个真实案例说明“AI自我修改”翻车的样子。


最近,一篇名字有点“吓人”的论文在AI圈流传开来。

标题叫《The Last AI Built by Humans: Toward Genuine Recursive Self-Improvement》,直译过来就是《人类打造的最后一个AI:走向真正的递归自我改进》。

光看标题,很容易联想到那句流传已久的话——“第一台超智能机器,将是人类需要做的最后一项发明”。这句话最早由数学家 I. J. Good 在上世纪六十年代提出,是“递归自我改进(Recursive Self-Improvement,RSI)”这个概念最早的雏形之一,此后一直是AI安全讨论里最具分量、也最容易被过度解读的话题之一。

但这篇论文的野心不在制造焦虑,而在“祛魅”:它想搞清楚的是——今天所说的“AI自我改进”,到底改进到了哪一步?哪些是真正意义上的“递归”,哪些只是套了层“自我进化”话术的传统自动化?

这正是这篇论文最有价值的地方。

先把概念说清楚:RSI到底“递归”在哪

论文给出的定义是:RSI是一个自主的、闭环的过程——AI系统通过与任务、环境或其他智能体的持续交互,把获得的经验和反馈自主转化为对自身的持久性改变(可以是模型参数、智能体的工具与流程配置,也可以是改进策略本身),并且这种改变还会进一步影响未来“生成、评估、筛选、沉淀改进”这套机制本身。

这个定义里,藏着一个很容易被忽略、却是全篇论证的关键的区分:“改进结果”和“改进机制”是两回事。

  • 你让AI帮你修一个bug,AI改好了,这是改进了一个输出;
  • 但如果AI在修bug的过程中,还顺手把“自己下一次该怎么定位bug、怎么验证修复”的方法论也改进了,并且这套新方法论会被后续任务继续沿用——这才触碰到了RSI的门槛。

论文专门用一个概念——B0:任务内AI改进(In-Task AI Improvement)——来标注这条边界。在B0阶段,AI可以在当前任务里反复修订、校验、择优,但改进不会作为“持久系统状态”被保留到下一个独立任务里。论文把B0定义为非RSI的参照水平,一句话总结就是:“只有输出变了,系统没有变”。

为了让“系统变了没变”这件事有据可查,论文还拆解了一个改进闭环(Improvement Loop)的解剖结构,定义了九个角色:AI系统、系统状态、经验、改进目标、改进者(Improver)、改进策略、验证者(Verifier)、被接受的改进、继任系统(Successor)。用一张图来看会更直观:

这张图画的是论文定义的“改进闭环”结构。论文反复强调:判断一个系统是不是在做RSI,要回答三个问题:

  • 闭环在哪里闭合?
  • 什么被保留并传给下一轮?
  • 哪些关键决策仍然掌握在人手里?

论文也顺带把RSI和三个容易混淆的近邻概念做了区分(持续学习、AutoML、智能体AI),核心结论是:这三者通常只自动化了“生成候选方案”或“保留状态”其中一部分,而很少让改进机制本身变成可以被系统自己修订、并在后续轮次里复用的对象——而这恰恰是RSI区别于它们的关键。

论文的尺子:AI的能力天花板,还剩多少没被捅破

在提出框架之前,论文先做了一件很扎实的事——量化“当前大模型到底进步到哪了”。

作者团队构建了一个新指标,叫 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最有可能率先撕开口子的地方。

论文的骨架:自主性五级框架(L1-L5)

搞清楚“还差多远”之后,论文给出了整篇工作最核心的贡献——一套自主性五级框架,用来衡量一个自我改进系统,究竟把多少“改进责任”从人类手里转移到了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到底能自己做多少决定”这个问题,而是把答案一级一级、一个案例一个案例地摆出来。

落地场景:不同领域,不同的反馈速度

自主性框架描述的是“闭环的结构”,但同样的闭环结构,落到不同领域里,能跑多快完全取决于反馈的成本和可靠性。论文选了四个场景做对比:

  • S1 科学发现:开放式探索,实验成本高,反馈信号有时甚至说不清“到底是哪一步错了”;
  • S2 具身智能:智能体靠自己的动作产生经验,物理试错的成本和可重复性都受限;
  • S3 软件工程:开发的产物和开发的智能体本身都可以被可执行的方式修改和测试,是四个场景里反馈最“干净”的一个;
  • S4 医疗:试错机会被严格限制,反馈延迟且异质,改进的有效性还可能因病人群体或机构不同而变化。

论文的结论也很直接:软件工程之所以是当前RSI进展最快的场景,本质上是因为它的反馈机制最接近“自动化验证”——有测试、有可执行环境、有明确的成功标准;而医疗、科学发现这类反馈稀疏、成本高、责任重大的领域,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会不会失控,不如先把“AI到底自己做了多少决定”这件事,一级一级地说清楚。

这或许才是这篇论文,比标题更值得被记住的地方。


论文信息

  • 标题:The Last AI Built by Humans: Toward Genuine Recursive Self-Improvement
  • 论文地址:https://arxiv.org/html/2609.11873v1
  • 项目主页:https://theseus-labs-rsi.github.io/

还在为写 Agent 框架频频死循环、上下文爆炸而束手无策?我的新专栏 《从0 开始构建 Agent Harness》 将带你:

  • 抛弃臃肿框架,回归“驾驭工程 (Harness Engineering)”的第一性原理
  • 用 Go 语言手写 ReAct 循环、并发拦截与上下文压缩引擎等,复刻极简OpenClaw
  • 构建坚不可摧的 Safety Middleware 与飞书人工审批防线
  • 在底层实现 Token 成本审计、链路追踪与自动化跑分评估
  • 从“调包侠”进化为掌控大模型边界的“AI 操作系统架构师”

扫描下方二维码,开启从 0 开始构建Agent Harness 的实战之旅。


还在为“复制粘贴喂AI”而烦恼?我的新专栏 《AI原生开发工作流实战》 将带你:

  • 告别低效,重塑开发范式
  • 驾驭AI Agent(Claude Code),实现工作流自动化
  • 从“AI使用者”进化为规范驱动开发的“工作流指挥家”

扫描下方二维码,开启你的AI原生开发之旅。


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

Go 内存分析要“动大手术”了:pprof 提案拟为 heap profile 补上失踪的另一半

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 内存诊断工具链最重要的一次升级。

【文章要点】

  • 痛点:默认配置下,存活堆常常占不到进程真实内存的 50%,“死堆”、协程栈、运行时开销、非 Go 内存都被排除在外;
  • 方案:不新建独立 profile,而是给已有 heap/alloc profile 加一个 memory_space 采样类型,兼顾发现性与兼容性;
  • 结构:RSS → Go Memory / Non-Go Memory → Heap(Live / Dead)/ Stack / Runtime 的完整层级树;
  • 权衡:profile 体积预计增大约 30%(未压缩)/ 10%(压缩后),社区正讨论是否需要 GODEBUG 开关;
  • 命名之争:从最初的 “rss” 改为 “memory”,容器化时代术语习惯是重要理由;
  • 进展:评审意见为“临时通过,等待正式实现验证”,尚未合入主线。

开场:一个每个 Go 运维都遇到过的“灵异事件”

深夜,告警响了:某 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)”的评审结论。

真相:为什么 inuse_space 只讲了一半的故事

要理解这份提案,得先搞懂 Go 内存诊断的“三件套”以及它们为什么互相“对不上账”:

  • 进程 RSS:操作系统内核给出的物理内存占用,是运维和 K8s OOM Killer 最关心的数字;
  • runtime/metrics:Go runtime 暴露的一堆细粒度内存计数器,专业但零散;
  • heap profile(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 这三份数据——这本身就是一种反人类的工程体验。

提案核心方案:给 heap profile 装上“上帝视角”

提案最初的方案是新增一个独立的 “rss” profile 类型,但经过多轮 runtime 诊断会议和 proposal review 的打磨,方案演化为:不新建 profile,而是给现有 heap/alloc profile 追加一个新的采样类型 memory_space,并将其设为默认展示类型:

pprof.Lookup("heap").WriteTo(f, 0)

这样设计有两个直接好处:

  1. 发现性拉满:用户不需要知道有个新 profile 存在,只要照常调用现有 API,看到的就是升级后的完整视图;
  2. 不破坏现有生态:持续性能剖析(continuous profiling)客户端通常不依赖 default_sample_type 字段,因此这一改动理论上只影响 pprof UI 的交互式使用者,不会打破已有的自动化采集链路。

新增的 memory_space 采样类型要回答的核心问题是:在最近一次标记终止(mark termination)时刻,进程的内存到底花在哪了?

内存全景图:一张图看懂 memory_space 的层级结构

提案给出的分层结构相当细致,基本上是把 /memory/classes/... 运行时指标体系和 pprof 的调用栈归因能力做了一次“缝合”。整体逻辑可以用下图概括:

有几个关键点:

  • 如果 OS/内核能读到 RSS,且 RSS ≥ Go Memory,才会显示顶层的 RSS 帧,并把 Non-Go Memory 计算为 RSS - Go Memory;
  • 如果 RSS 不可用,或者出现 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 团队“谨慎但不保守”的风格:

  • 态度积极:Austin Clements 明确表示“这确实能解决长期以来大家对 heap profile 的困惑”;
  • 追问细节:是否包含 data segment(文件映射但私有可写的内存)?作者确认在 Linux 上会包含在 RSS 统计中(对应 /proc/self/statm 的第二个字段);
  • 虚拟内存 vs 物理内存的老生常谈:Go runtime 主要统计的是虚拟内存,而 RSS 是物理内存,两者天然存在语义鸿沟,评审中特别讨论了这一点,最终方案选择用“警告帧”的方式坦承这种局限,而不是假装两者完全等价。

最有意思的是一场关于命名的争论。提案最初打算叫 “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 的加入而产生混淆。

这对 Go 开发者意味着什么

如果这份提案最终落地,日常的内存排查工作流可能会发生实质性变化:

  • 不再需要手动拼凑 RSS、runtime/metrics、heap profile 三份数据做“三方对账”;
  • 一次 pprof.Lookup(“heap”).WriteTo(f, 0) 调用,就能拿到从 RSS 到具体调用栈的完整归因链路;
  • 容器化环境下的成本优化和事故排查(incident response),可以直接在火焰图层面看到“非 Go 内存”占了多大比例,而不必再去猜测是不是 cgo、mmap 或者共享库在“背锅”;
  • 对于持续性能剖析(continuous profiling)平台和 APM 厂商而言,需要提前评估 profile 体积增加带来的存储和传输成本。

需要提醒的是,目前该提案处于“临时通过,等待正式实现验证”阶段——评审委员会明确表示,希望看到一份“非 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》 将带你:

  • 抛弃臃肿框架,回归“驾驭工程 (Harness Engineering)”的第一性原理
  • 用 Go 语言手写 ReAct 循环、并发拦截与上下文压缩引擎等,复刻极简OpenClaw
  • 构建坚不可摧的 Safety Middleware 与飞书人工审批防线
  • 在底层实现 Token 成本审计、链路追踪与自动化跑分评估
  • 从“调包侠”进化为掌控大模型边界的“AI 操作系统架构师”

扫描下方二维码,开启从 0 开始构建Agent Harness 的实战之旅。


你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?

  • 想写出更地道、更健壮的Go代码,却总在细节上踩坑?
  • 渴望提升软件设计能力,驾驭复杂Go项目却缺乏章法?
  • 想打造生产级的Go服务,却在工程化实践中屡屡受挫?

继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!

我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。

目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!


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