阅读视图

发现新文章,点击刷新页面。
🔲 ☆

一天重写 JSONata,我用 400 美元干掉了公司 50 万美元的 K8s 集群

本文永久链接 – https://tonybai.com/2026/04/01/rewrote-jsonata-in-golang-with-ai

大家好,我是Tony Bai。

过去的几年,我们见证了 AI 编程工具从“玩具”到“神器”的进化。无数开发者都在分享自己效率翻倍的喜悦。

你有没有想过,用 AI 来完成一次“外科手术式”的精准重构,一天之内,就能帮你把公司每年烧掉的 50 万美元(约 360 万人民币)的服务器成本,直接砍到零?

这听起来像天方夜谭,但它真实地发生了。

就在前几天,以色列安全公司 Reco 的工程师 Nir Barak 发表了一篇极其硬核的博客。他详细复盘了自己是如何在一天之内,花费了仅仅 400 美元的 Token 费用,利用 AI 将一个用 JavaScript 编写的核心组件 JSONata,完美地重写为了纯 Go 版本,最终为公司节省了每年 50 万美元的开销,并带来了 1000 倍的性能提升。

这不仅仅是一个“AI 真牛逼”的简单故事。它背后揭示的,是一套足以改变我们未来架构选型和技术债偿还方式的“AI 驱动重构(AI-Driven Refactoring)”实用方法。

跨语言 RPC,微服务架构中最昂贵的“性能税”

要理解这次重构的意义有多么重大,首先得看看 Nir Barak 的团队曾经陷入了多深的泥潭。

他们的核心业务是一个用 Go 编写的高性能数据管道,每天处理数十亿的事件。但其中有一个环节,需要用到一个名为 JSONata 的查询语言(你可以把它想象成带 Lambda 函数的 jq)来执行动态策略。

尴尬的是,JSONata 的官方实现是 JavaScript 写的。

这就导致了一个极其痛苦的架构:他们的主业务 Go 服务,为了执行这些规则,不得不去远程调用(RPC)一个专门部署在 Kubernetes 上的庞大的 Node.js 服务集群。

这个“小小的”跨语言调用,给他们带来了三大噩梦:

  1. 恐怖的成本:为了扛住流量,这个 jsonata-js 集群常年需要维持 300 多个 Pod 副本,光是这部分,每年就要烧掉 30 万美元的计算资源。
  2. 惊人的延迟:一次最简单的字段查找,比如 email = “admin@co.com”,在 Node.js 内部执行可能只需要几纳秒。但算上序列化、跨进程网络往返的开销,一次 RPC 调用在啥也没干之前,150 微秒的延迟就先进来了。对于一个每天处理几十亿事件的系统来说,这简直是灾难。
  3. 意想不到的运维黑洞:随着业务增长,Pod 数量一度多到耗尽了 Kubernetes 集群的 IP 地址分配上限!

Nir Barak 的团队当然也尝试过各种小修小补:优化表达式、加缓存、甚至用 CGO 把 V8 引擎直接嵌进 Go 里……但这些都只是“头痛医头”,无法根治“跨语言”这颗毒瘤。

Cloudflare 的“抄作业”哲学

转机发生在前几周。Nir Barak 看到了 Cloudflare 那篇刷爆全网的文章《我们如何用 AI 在一周内重构 Next.js》。

Cloudflare 的做法极其“暴力”且有效:他们没有让 AI 去创造新东西,而是把 Next.js 现成的spec,以及包含几千个 case 的官方测试套件(Test Suite)直接扔给大模型,然后对 AI 下达了一个简单粗暴的指令:

“我不管你怎么实现,给我写一个能在 Vite 上跑通所有这些测试的 API 就行!”

Nir Barak 看到这里,瞬间被点醒了:“我们面临的问题一模一样!我们也有 jsonata-js 官方那套包含 1778 个测试用例的完整套件啊!”

与其让 AI 去搞创新,不如把它变成一个任劳任怨、24 小时待命的“代码翻译工”!

于是,他花了一个周末,用 AI 制定了一个极其清晰的“三步走”作战计划:

  1. 第一步(人类智慧):用 Go 语言把 jsonata-js 的测试套件先“翻译”过来。
  2. 第二步(AI 体力):把 JSONata 2.x 的官方文档和规范全部喂给 AI。
  3. 第三步(测试驱动):对 AI 下达指令:“开始写 Go 代码,目标是跑通第一步的所有测试用例。”

第二天,他按下了“开始键”。

7 小时,400 美元,13000 行 Go 代码

接下来的故事,充满了令人肾上腺素飙升的极客快感。

Nir Barak 坐在电脑前,看着 AI Agent 像一台失控的缝纫机一样,疯狂地生成 Go 代码、运行测试、读取报错、然后自我修正。

整个过程被划分成了几个“波次(Waves)”:先实现核心解析器,再实现内置函数,最后处理各种边缘 case。

在 AI 与测试用例的左右互搏之下,仅仅 7 个小时 后,奇迹发生了:

一个包含 13,000 多行纯 Go 代码的、名为 gnata 的全新 JSONata 实现诞生了。它完美通过了官方所有的 1778 个测试用例。

而这整个过程的成本呢?

400 美元的 Token 费用。

Nir Barak 在博客中晒出了一张截图,数据显示,在重构 gnata 的那一天,AI 生成的代码占比高达 91.7%

当他把这个 PR 提交到公司内部时,立刻有人质疑 ROI(投资回报率)。而他的回答简单粗暴:

“上个月,jsonata-js 集群的成本是 2.5 万美元。现在,是 0。”

百倍性能与意外之喜:“手术刀式”重构的深远影响

成本降为零已经足够震撼,但性能上的收益更是堪称“恐怖”。

这还只是开始。由于 gnata 是纯 Go 实现,Nir Barak 团队得以进行更深度的“魔改”:他们设计了一套两层评估架构。对于简单的字段查找,gnata 直接在原始的 JSON 字节流上操作,实现了 零堆内存分配(Zero Heap Allocations)!只有遇到复杂表达式时,才会启动完整的解析器。

在接下来的两周内,他们乘胜追击,用 gnata 的批量处理能力,替换掉了主数据管道中另一个极其臃肿、靠启动上万个 Goroutine 来并发处理规则的旧引擎。 结果:又省下了每年 20 万美元。

短短两周,两次“外科手术式”的重构,总共为公司节省了每年 50 万美元的开销。

最让人意想不到的是,这次重构还带来了组织层面的“意外之喜”:

gnata 是公司内部第一个完全由 AI Agent 大规模参与生成的 PR。在 Code Review 的过程中,团队成员被迫去学习如何分辨“AI 真正发现的并发 Bug”和“AI 瞎操心的代码格式问题”。这次经历,为他们后续制定全公司的 AI Code Review 规范积累了宝贵的实战经验。

小结:我们不再只是“氛围感编码”

在文章的结尾,Nir Barak 提到了 AI 大神 Andrej Karpathy 最近的观点,大意是:

“编程正在变得面目全非。在底层,深厚的技术专长正成为比以往任何时候都更强大的‘乘数效应放大器’。”

Nir Barak 感慨道,直到最近,他自己都对那种完全由 AI Agent 生成代码的“氛围编码(Vibe coding)”持怀疑态度。但 2026 年 2 月,成为了一个连他这样固执的开发者都无法忽视的“拐点”

gnata 的诞生,标志着我们不再只是用 AI 去写一些无关紧要的玩具项目。在拥有明确测试用例和边界规范的前提下,AI 已经具备了对生产环境核心组件进行“手术刀式重构”的惊人能力。

你准备好拿起这把名为“AI”的手术刀,去切掉你系统里那些最昂贵、最臃肿的“技术肿瘤”了吗?

资料链接:https://www.reco.ai/blog/we-rewrote-jsonata-with-ai


今日互动探讨:

在你的公司里,是否存在类似的“异构技术栈”调用导致的性能瓶颈或成本黑洞?你有没有想过,可以用 AI + 测试用例的方式,对某个核心组件进行“代码翻译”式的重构?

欢迎在评论区分享你的架构痛点与大胆构想!


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

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

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


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

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

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

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

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


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

© 2026, bigwhite. 版权所有.

🔲 ☆

315曝光AI投毒反而会火?GEO灰产可能被“央视背书”撑大?

昏黄桌面上的一叠315晚会相关报纸与电视机屏幕剪影,屏幕里浮现“AI”“警示”字样的字幕条,旁边散落着放大镜、钢笔与便签纸,羊皮纸,钢笔彩色手绘的统一风格。

315晚会曝光“AI投毒”,可能带来反效果

315晚会报道了“AI投毒”,这事可能会起到反效果。自从315报道说苹果、iPhone质量不好之后,我对315晚会的看法就一直比较谨慎。这一次,315终于瞄准了AI,讲的是所谓的“GEO投毒”。

什么是GEO投毒:生成式引擎优化的“投毒”产业链

一条“灰色产业链”剖面图:左侧是GEO服务商的小办公室电脑屏幕,中间是发稿平台的网页列表与上传按钮,右侧是内容工坊批量写稿的流水线,最末端是用户手机上的AI回答气泡,羊皮纸,钢笔彩色手绘的统一风格。

现在出现了一种叫GEO投毒的方式,也就是“生成式引擎优化”的投毒手段,而且已经形成了产业链。涉及的企业包括所谓的GEO服务商(例如被提到的“力擎GEO”)、各大发稿平台,以及虚假内容的制作方。

表面上受害的可能是豆包、千问、百度的文心一言等平台,但真正受害的是用户和消费者:大家通过这些AI平台看到假消息,甚至可能因此买到假货。

本质是SEO的AI升级:更精确、更高效率

所谓GEO投毒,本质上其实就是SEO(搜索引擎优化)的AI版升级,只是更精确、更高效率。它的核心逻辑是通过大量虚假信息投喂,去影响、控制AI模型的推荐与回答。

需要注意的是,这里绝对不涉及对模型本身的训练。很多模型在回答问题时,会先去搜索,再基于搜索结果生成内容;一旦搜索到的结果是错的,模型给出的推荐和回答自然也会错。

运作流程:发布—收录—引用—推荐—转化

俯视视角的流程图场景:五个节点从左到右依次是“发布”的稿件、被蜘蛛爬虫抓取的网页、“引用”的对话气泡、“推荐”的榜单卡片、“转化”的购物车与付款按钮,节点之间用箭头连接,羊皮纸,钢笔彩色手绘的统一风格。

它的运作流程大致是:发稿平台发布内容,AI自动爬取并收录,随后在用户提问时被引用、被推荐,最后实现转化。

相比传统SEO,这套玩法更麻烦的地方在于:AI更强调“故事要自洽”。如果有大量稿件都围绕同一件事,从不同角度反复印证、互相佐证,AI就更容易认为它可信,从而更倾向于推荐。

只要AI的输出存在可被总结的规律,就一定会有人顺着这些规律,把“愿意付钱的信息”推到用户面前去。至于信息真假并不重要,重要的是有人愿意为它付钱。

按效果收费的灰产生意:必须借助“可信平台”

这类服务的商业模式往往是按效果收费。过去可能需要一年花上亿的广告费,现在号称花几百万“投个毒”就能搞定。整个链条一般包括:

  • 第一,GEO服务商;
  • 第二,发稿平台;
  • 第三,虚假内容生产方。

做GEO不可能只靠自己建个新网站,因为AI会对信息源的可信度做排名,新站往往不被信任、也未必会被爬取,所以必须借助看起来更“可信”的内容平台来承载这些稿件,等AI爬虫收录后再进入推荐链路,最终让消费者上当。

“投毒”这个说法为什么更容易传播

“投毒”这个叫法也很有传播效果。表面叫投毒,实际上就是内容灌水、虚假信息堆叠,但如果只说“灌水”,大家会觉得不过如此;一说“投毒”,就会让人感觉与自身安全强相关,更容易引发关注。

315案例:虚构“阿波罗手环”如何被AI当成事实

一个并不存在的“阿波罗手环”产品伪广告拍摄现场:手环被摆在高光灯下,旁边是写着“专家推荐”“获奖”的道具奖杯与证书,背景墙是发稿平台页面的投影,羊皮纸,钢笔彩色手绘的统一风格。

315曝光的案例里,有一个典型做法是编造根本不存在的产品,例如“阿波罗手环”。虚假内容制作方把相关信息投放到平台上,平台方不做核实就直接发布。按广告法,发布广告前应当核实,但这些平台并不在意。

发布后不久,推荐引擎就可能开始推荐该产品,AI也能够回答“阿波罗手环是什么”之类的问题,相当于完成了“入库”。接着他们持续发布更多文章,比如“阿波罗手环被专家推荐”“阿波罗手环有神奇功能”“阿波罗手环在哪里获奖”等等,通通不经事实核查。

AI为什么更容易“信”:故事自洽 + 来源打分

对AI来说,它会默认:这些刊载文章的网站应该核实过内容,甚至AI还会给这些网站打分、判断可信度。当它把多篇文章拼在一起,发现故事“严丝合缝、环环相扣”,就更容易把它当成可信事实进行推荐。过两三天你再去问“买什么手环”,它就可能把广告词整套推出来。

曝光后平台自救:豆包更快,DeepSeek更慢

两台并排的机器人助手:左侧迅速举起“已更正/虚假信息提醒”的牌子,右侧还在翻阅厚厚的资料与错误答案草稿;背景是同一条关于手环的提问对话框,羊皮纸,钢笔彩色手绘的统一风格。

事情曝光后,AI厂商开始反应和自救。内容上“最一本正经胡说八道”的被认为是DeepSeek,原因在于它缺乏大公司那种庞大的内容运营与核实能力,因此更容易出现看似严谨但实际错误的回答。网上也出现不少“DeepSeek说了这个、说了那个”的文章,在今日头条等平台还有流量,但越是这样越要警惕。

较早进行修正的是豆包。再问到阿波罗手环时,豆包会提示这是315曝光的虚假信息,提醒不要购买。一方面字节跳动背后有今日头条、抖音等平台,具备较强的“上层干预”能力;另一方面,GEO推荐特别依赖时效性。

旧新闻沉淀太久会降低推荐权重,而一批“最新专家推荐”的新文章更容易被推上来。此时315作为央视来源,权威性和时效性都很强,权重极高,能够迅速覆盖此前的虚假内容。豆包也做了部分道歉与更正;千问、百度文心一言等也快速更新,提示相关产品并不存在或为虚假信息。相对之下,DeepSeek的响应慢一些。

“会讲故事”在AI时代的另一面:灰产自动拼故事

自动发稿机的机械臂在拼贴“故事碎片”:功能清单、专家头像、获奖徽章、用户好评截图被一张张贴在同一块公告板上,最后变成一篇“看似完整”的文章卷轴,羊皮纸,钢笔彩色手绘的统一风格。

过去我曾提到“AI时代需要会讲故事的人”。现在灰产其实就是用自动发稿机把一个故事拼齐,投放到互联网上,再让AI自己把这个故事“拼成事实”。

一个新产品被描述出各种功能,再配套“专家好评”、再配套“获奖信息”,并持续更新、持续讲述,最终AI推荐引擎或聊天引擎就可能把它当成真实事实输出,很多人也会选择相信。

讲故事本身并不是错,315这次反而验证了:AI确实可能被污染;同时,如果你不会按AI偏好的方式把内容组织好,可能你的产品连被AI“露出”的机会都没有。

GEO为何比SEO危险:三个原因

从概念上说,GEO是SEO的升级,但比SEO危险得多。SEO既有正面用法,也有灰色用法。正面用法是优化自己的网站,让更多人看到内容;灰色用法包括站群互引、刷外链、在各处大量发稿和刷评论等。

比如谷歌早期用PageRank根据“被引用次数”来评估网站可信度,于是就有人自己建大量网站相互引用来抬升排名。评论区刷屏也是常见灰产,甚至会被抓取并影响内容传播。谷歌与这些灰色手段对抗几十年,有一定进展,但手段更隐蔽、成本更高。

很多事情并非“坏就能干掉”,现实往往是持续加强防护与检验,让违规成本不断上升;最终愿意花钱的人依然可能通过竞争获得露出。

SEO产业链存在20多年,较为稳定,也形成了一套规则与“行规”。链接农场、发稿平台等很多资源在SEO时代就已存在。SEO的收益相对稳定:花多少钱、得到怎样的效果,大致可预期。

原因一:从“给一堆链接”变成“给唯一答案”

左侧是传统搜索结果页面的一列蓝色链接与摘要,右侧是聊天机器人给出的单一结论卡片并配“专家背书”印章,用户手指正要点击确认,强烈对比构图,羊皮纸,钢笔彩色手绘的统一风格。

搜索引擎通常给出一堆结果,用户还需要自己点击、再判断一轮,虽然判断错误的概率很大,但至少还有一层筛选;搜索引擎也可以把责任推给用户:“链接都给你了,是你自己点的。”

而GEO往往给的是唯一答案:你问“哪个减肥药最好”,它直接给你一个结论,并配上理由、专家背书。这样一来,AI更难逃避责任,用户也更容易相信这一条“已经帮你挑过”的唯一结果。

很多人每天与AI沟通的时间甚至可能超过与家人的相处时间,信任建立后就更危险。

原因二:GEO更像黑盒,优化与纠错路径模糊

SEO的运作相对透明。为什么某网站排名第一,可以从外链、域名、权重等规则解释,谷歌也会公开部分排序思路、接受质询。

GEO则更像黑盒:为什么这次推荐A、下次推荐B,连模型开发者自己都未必说得清楚,而且输出不稳定,导致优化空间和纠错路径都很模糊。

原因三:平台可控性更差,版本迭代不确定

搜索引擎可以小步快跑地更新算法,针对某个节点做精细优化;而大模型版本更新常常是整体变化,无法保证“下一版一定更好”。对于持续投毒的灰产来说,平台的迭代速度和治理可控性都存在不确定性。

为什么说315曝光可能“适得其反”

一只手拿着扩音器写着“曝光”,声波向外扩散,另一侧是灰产商人看到声波后把硬币倒入“预算”盒子并点亮“试一试”的开关,形成“刺激尝试”的画面隐喻,羊皮纸,钢笔彩色手绘的统一风格。

我认为315对GEO投毒的曝光,反而可能适得其反。当前GEO市场的真实现状是:确实可能有效果,但效果很差、极不稳定,也不可验证。

原因之一是大模型输出不固定,同样的问题每次结果可能不同,因此很难像SEO那样承诺“花钱就排到前三,排不到就扣钱”。此外,GEO的成交归因也做不了:SEO可以通过链接、通道号、参数追踪知道成交来自哪里,从而结算费用;但大模型会把路径信息抽取、过滤掉,最终你很难证明成交到底是不是因为某次投放导致。

因为效果不可验证、归因不清,很多服务商其实并没有挣到钱,商家也不太敢持续给预算,而且若想维持效果还得不停投放、持续更新,成本与不确定性都更高。

“反效果”的逻辑:报道越高调,越可能刺激尝试

315曝光之后的“反效果”在于:原本很多企业还在犹豫是否要花钱尝试,现在一看上了315,可能就会挪出预算去试一试。“有枣没枣打三竿子”,预算一旦进来,灰产市场规模就可能被撑起来。原本在生死线边缘挣扎的投毒厂商,反而可能因此活下来。

更关键的是,315等于给“GEO投毒有效”贴了央视的金字招牌,而此前这种效果本来是不可验证的。有了金钱推动,技术会发展,模式会迅速膨胀。这有点像禁果效应:越是高调报道危害,越可能让原本不知道的人产生“我得试试”的冲动。

曾经美国媒体大幅报道摇头丸的危害后,反而让更多年轻人开始尝试,类似逻辑也可能发生在GEO投毒上。

治理是否可能:无法根治,只能长期攻防

至于治理GEO投毒有没有办法,结论是:不可能彻底干掉,就像SEO治理一样,20多年也没被彻底消灭,只能在攻防中不断迭代。平台方持续识别旧把戏,作弊方持续发明新套路,攻防成本不断上升。最终,愿意花钱的人仍可能通过各种方式获得流量。

与SEO时代的关键差别:平台治理动力不完全一致

GEO治理与SEO治理还有一个本质差别:SEO时代搜索引擎厂商有强动力治理,因为它们卖广告,治理越严,越能把需求引导到“正规投放”。但GEO时代,这种原始动力并不完全一致。

虽然OpenAI开始提广告,谷歌也号称会在AI Overview里放广告,但与GEO投毒并非完全直接对应,中间隔着一层,这也意味着治理路径仍需要继续摸索。

目前可能的治理方向

方向一:源头验证与信任分级

第一是源头验证法,也是谷歌、OpenAI等常见路径:给信息源建立信任等级,比如更信任路透社、央视等权威来源,对个人网站、社交媒体设置不同的信任指数,综合判断可信度,并输出“高、中、低”的置信评价。

它有一定效果,但问题是:被信任的网站也可能胡说八道,收录文章也未必核实,只能通过长期积累不断降低假消息比例。

方向二:建立“事实底座”(以Grok Wiki为例)

一座“事实底座”图书馆:中央是被锁链与印章保护的档案柜写着“Wiki”,旁边AI助手拿着检索卡对照外部新闻剪报与时间戳,进行校验比对,羊皮纸,钢笔彩色手绘的统一风格。

第二是更极端的方式,例如xAI的做法:一方面使用X上的最新数据进行一定验证,另一方面建立一个叫Grok Wiki的“事实底座”,把经过AI筛选和人工确认的信息存下来,再回答问题时去对照校验。

这样确实更难骗,Grok也被认为是最难被诱导的一类助手:消息新、且背后有一个“唯一正确”的底座。但风险也很大,因为Grok Wiki里的观点可能存在巨大争议。

马斯克认为的“真相”未必是所有人认可的真相;当“唯一真相”被确立,就可能被绑架或出现指鹿为马的危险。在某些地区如果缺少类似X这种可开放讨论的平台,又要建立唯一正确解,风险会更高。

方向三:用户责任与免责声明

第三是用户责任,也就是平台用免责声明让用户自我验证。现在无论是ChatGPT、Gemini还是Grok,页面底部往往都会提示:内容由AI生成,不能保证正确,使用前请自行核实。这在一定程度上也是一种责任转移。

现实中往往是多种方式混合使用:有内部事实库、有源头信任体系,也有免责声明。只不过有的事实库对外公开(如Grok Wiki),有的并不公开;某些地区也可能存在不对外公开的事实库,并且对很多内容“说不清楚”。

最后的建议

对消费者

  • 不要相信GEO投毒未来能被彻底根治,这不可能。任何人如果承诺“彻底搞定”,都要警惕。
  • 也不要太相信AI的结果,AI生成过程本质上不可控,尤其在医疗、金融、消费决策等领域要格外小心。
  • 更不要因为“某个模型说得很肯定”就完全相信,AI往往会一本正经地胡说八道,越是信誓旦旦越要小心。

对内容创作者和企业

仍然要把故事讲好,把内容按AI更容易理解和收录的方式组织好。这里指的是正向的内容包装与表达,而不是灌水或投毒;如果不做这件事,在AI时代可能会吃亏。

对我自己以及每一位听众

还是继续讲好自己的故事,保持清晰统一的人设,做好“个人的GEO”。感谢大家收听,也欢迎大家点赞、点小铃铛、加入DISCORD讨论群,有兴趣有能力的朋友也欢迎加入付费频道。再见。


背景图片

🔲 ☆

真相调查:Go 语言真的消灭了 Undefined Behavior 吗?

本文永久链接 – https://tonybai.com/2026/03/16/go-language-eliminated-undefined-behavior-truth-investigation

大家好,我是Tony Bai。

在系统编程的古老传说中,流传着一个关于“鼻恶魔”(Nasal Demons)的笑话。

这个梗源自 comp.std.c 新闻组,它是对 C/C++ 语言中“未定义行为”(Undefined Behavior,以下简称 UB)最生动也最恐怖的诠释。根据 ISO C++ 标准,如果你的代码触犯了 UB(例如数组越界、有符号整数溢出、空指针解引用),编译器可以“为所欲为”。

这种“为所欲为”不仅包括程序崩溃,还包括产生错误的结果、损坏数据,甚至——虽然只是笑话——让恶魔从你的鼻孔里飞出来。换句话说,一旦触碰 UB,程序的所有保证瞬间失效。

2009 年,Go 语言横空出世,高举“云原生时代系统语言”的旗帜,承诺提供比 C++ 更高的安全性、更快的编译速度和更简单的并发模型。Go 的拥趸们津津乐道于它的内存安全特性,仿佛 Go 已经彻底终结了 UB 的噩梦。

但真相果真如此吗?

近日,我翻阅了一份珍贵的历史资料——2013 年发生在 golang-nuts 邮件组的一场深度辩论。对话的一方是 Go 语言曾经的顶级贡献者 Dave Cheney,另一方是 Go 核心团队成员、gccgo 的作者 Ian Lance Taylor。

这场发生在这个语言童年时期的对话,揭示了一个令人背脊发凉又引人深思的事实:Go 并没有完全消灭未定义行为,它只是将 UB 赶进了一个更隐秘、更危险的角落——并发。

本文将带你层层剥开 Go 语言规范的表皮,调查“未定义行为”在 Go 中的真实生存状态,并探讨这对我们编写高质量代码意味着什么。

用“定义”换取“安全”——Go 的显式哲学

要理解 Go 做了什么,我们首先得明白 C/C++ 为什么保留 UB。Ian Lance Taylor 指出,C/C++ 保留 UB 本质上是为了性能——允许编译器假设“坏事永远不会发生”,从而进行激进的优化。

Dave Cheney 的疑问直击灵魂:“Go 规范中几乎看不到‘undefined’这个词,这种设计如何影响了 Go 的安全性与性能?”

答案是:Go 选择了一条确定性(Determinism)优先的道路。Go 语言规范以一种近乎偏执的态度,将绝大多数在 C/C++ 中属于 UB 的行为,都进行了严格的“定义”。即便是在错误场景下,Go 也要保证行为是可预测的

整数溢出的“确定性”承诺

在 C 语言中,有符号整数(Signed Integer)的溢出是经典的 UB。编译器有权假设溢出永远不会发生,从而将 x + 1 > x 优化为恒真(Always True),这曾导致过无数的安全漏洞。

但在 Go 语言规范中,对此有着截然不同的定义:

无符号整数:运算结果严格按照 2^n 取模。这意味着高位被丢弃,程序可以依赖这种“回绕(Wrap-around)”行为。

有符号整数:运算可以合法地溢出(legally overflow)。结果由有符号整数的表示方式(通常是补码)、运算类型和操作数确定性地定义。溢出不会导致运行时 Panic。

最关键的是,Go 规范明确禁止编译器进行危险的假设:“编译器不得假设溢出不会发生。例如,它不得假设 x < x + 1 总是为真。”

代码实证:

// https://go.dev/play/p/5CZVVU-SITX
package main

import "fmt"

func main() {
    // 1. 有符号整数溢出 (Signed Overflow)
    var a int8 = 127
    // 在 C 语言中这是 UB,但在 Go 中这是明确定义的
    b := a + 1
    fmt.Printf("int8: %d + 1 = %d\n", a, b)
    // 输出: 127 + 1 = -128 (确定性的回绕)

    // 2. 编译器禁止做的优化
    // 如果编译器假设溢出不发生,它会把这个判断优化掉
    if b < a {
        fmt.Println("发生溢出:b 确实小于 a")
    } else {
        fmt.Println("未发生溢出逻辑(Go 中不会走到这里)")
    }

    // 3. 无符号整数溢出 (Unsigned Overflow)
    var c uint8 = 255
    d := c + 1
    fmt.Printf("uint8: %d + 1 = %d\n", c, d)
    // 输出: 255 + 1 = 0 (严格的 Modulo 2^n)
}

Go这么做的代价是Go 编译器失去了一些数学优化机会(例如不能简单地消除某些循环边界检查)。但也消除了因编译器“自作聪明”而导致的逻辑崩塌,保证了不同平台下的行为一致性。

数组越界的“必杀令”

缓冲区溢出(Buffer Overflow)是网络安全史上最大的杀手。C/C++ 将越界访问视为 UB,允许攻击者通过越界读取敏感内存或覆盖返回地址,进而控制系统。

Go 对此零容忍:越界必须触发 Panic。

无论是在栈上分配的数组,还是在堆上分配的切片,Go 编译器都会在每一次访问操作前(除非能静态证明安全)插入一段 Bounds Check(边界检查)指令。一旦越界,程序立即停止,绝不含糊。

代码实证:

// https://go.dev/play/p/-CqDpIDr0BC
package main

import "fmt"

func main() {
    // 定义一个长度为 3 的切片
    s := []int{1, 2, 3}

    // 模拟一个动态索引(避免编译器在编译期直接报错)
    index := getIndex() 

    fmt.Println("尝试访问索引:", index)

    // 这里会触发 Runtime Panic
    // 错误信息明确:runtime error: index out of range [3] with length 3
    val := s[index] 

    fmt.Println("这行代码永远不会执行", val)
}

func getIndex() int {
    return 3
}

这种边界检查是在运行时(Runtime)介入,抛出 Panic,打印堆栈信息。因此会带来运行时性能损耗。虽然现代 Go 编译器引入了 BCA(边界检查消除)技术,但在无法静态分析的场景下,这就是必须缴纳的“安全税”。

空指针的“硬着陆”

在 C 语言中,解引用一个空指针是 UB。编译器有时会优化掉判空逻辑,因为它认为“既然你解引用了,那指针肯定不为空”,导致后续的安全检查失效。

Go 规定:解引用 nil 指针必须触发 Panic。

这通常是通过 CPU 的硬件异常(SIGSEGV)来捕获的。Go 运行时会接管这个硬件信号,并将其转化为一个可恢复的 Go Panic,而不是让进程直接 Core Dump 或进入不可预测的僵死状态。

代码实证:

// https://go.dev/play/p/hlyZks1dGRf
package main

import "fmt"

type User struct {
    Name string
}

func main() {
    var u *User // u 默认为 nil

    fmt.Println("准备访问 nil 指针...")

    // 在 C 中这是 UB,可能导致程序崩溃或更糟的情况
    // 在 Go 中,这不仅会 Panic,还可以被 Recover 捕获
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("捕获到恐慌:", r)
            // 输出: runtime error: invalid memory address or nil pointer dereference
        }
    }()

    // 触发 Panic
    fmt.Println(u.Name)
}

综上,我们可知:在单线程维度,Go 确实几乎消灭了 Undefined Behavior。它通过强制规定行为(Wrapping, Panicking),将“未定义”变成了“定义明确的错误”。即使程序写错了,它的错误方式也是确定的,而非随机的。

房间里的大象——数据竞争

如果文章到这里结束,那么 Go 就是一个完美的、绝对安全的语言。

但 Ian Lance Taylor 随后抛出了一个重磅炸弹:

“However, Go does have undefined behavior: if your program has a race condition, the behaviour is undefined.”
(然而,Go 确实存在未定义行为:如果你的程序存在数据竞争,那么行为就是未定义的。)

这就是 Go 语言安全神话中最大的裂痕。

在 Rust 中,编译器借用检查器(Borrow Checker)会在编译期阻止数据竞争,因此 Rust 可以自豪地宣称“无数据竞争”。但 Go 选择了更简单的并发模型,允许 Goroutine 共享内存。

一旦发生数据竞争(Data Race),即多个 Goroutine 同时访问同一块内存且至少有一个是写操作,Go 就不再提供任何保证。

为什么数据竞争是真正的 UB?

很多 Gopher 认为数据竞争只是“读到了旧数据”或者“计数器少加了 1”。这是一种极其危险的误解。在多核 CPU 和现代编译器优化的加持下,数据竞争在 Go 中可能导致内存安全破坏

这主要源于 Go 的多字数据结构(Multi-word Data Structures)

接口(Interface)的“撕裂”

Go 的 interface 在底层是由两个机器字组成的:{type_ptr, data_ptr}。

  • type_ptr 指向具体类型的元数据(如方法表)。
  • data_ptr 指向具体的数据值。

假设我们有一个全局接口变量 var i interface{},以及两个实现类型 type A 和 type B。

  • Goroutine 1 试图将 i 赋值为 A{}。
  • Goroutine 2 试图将 i 赋值为 B{}。

如果没有加锁,Goroutine 3 可能会读到一个“弗兰肯斯坦”般的怪物接口:它的 type_ptr 来自 A,但 data_ptr 却指向 B 的数据!

当你调用这个接口的方法时,程序会尝试用 A 的方法表去操作 B 的内存布局。这会导致什么?

如果运气好,你会得到Panic(类型断言失败或非法内存访问)。

反之,如果运气不好,那远程代码执行(RCE)的攻击者可以精心构造内存布局,利用这种类型混淆(Type Confusion)来劫持控制流。

切片(Slice)的“越界”

切片由 {ptr, len, cap} 三个字组成。数据竞争可能导致你读到了新的 len(变得很大),但 ptr 还是旧的(指向一个小数组)。结果是你拥有了一个长度远超底层数组容量的切片,这让你能够读取甚至修改不属于该切片的任意内存——这正是 C 语言缓冲区溢出的翻版。

这,就是 Go 中的 Undefined Behavior。 它不是“鼻恶魔”,但它是真实存在的安全黑洞。

那些“未指明”的灰色地带

除了致命的 UB,讨论中还涉及了 Go 语言规范中的另一种存在:未指明行为(Unspecified Behavior)实现定义行为(Implementation-Defined Behavior)

这些行为虽然不会导致内存破坏,但同样破坏了程序的“确定性”。

Map 的迭代顺序

在 Go 中,for k, v := range m 的顺序是故意未定义的。

Ian 解释说,这是为了防止开发者依赖某种特定的哈希实现顺序。Go 运行时甚至在每次迭代开始时引入了随机种子(迭代器会在map bucket 数组中随机选取一个起始位置向后遍历),强制让顺序变得不可预测。

这是一个非常有智慧的设计:通过强制随机化,逼迫开发者编写不依赖顺序的健壮代码。

表达式求值顺序:在“确定”与“未指明”之间

在 C/C++ 中,f(g(), h()) 中 g() 和 h() 谁先执行是未定义的(Undefined Behavior 或 Unspecified Behavior),这取决于编译器实现。

Go 语言规范对此做了更严格的规定,但依然保留了一块微妙的“灰色地带”。

确定的部分(Defined):

Go 规定,在求值表达式的操作数、赋值语句或返回语句时,所有的函数调用、方法调用和通信操作(Channel receive)都必须按照词法上从左到右的顺序执行。

例如,在赋值语句 y[f()], ok = g(h(), i()+x[j()], <-c), k() 中,函数调用和通信的发生顺序被严格锁定为:

f() -> h() -> i() -> j() -> <-c -> g() -> k()。

未指明的部分(Unspecified):

然而,规范同时也指出:并没有规定上述事件与表达式求值、索引操作、以及变量 y 的求值之间的顺序。

这意味着,虽然函数调用的相对顺序是固定的,但涉及副作用(Side Effects)的变量读写顺序可能是不确定的。来看 Spec 中的经典反例:

a := 1
f := func() int { a++; return a }

// x 可能是 [1, 2] 也可能是 [2, 2]
// 因为 a 的求值与 f() 的执行顺序未定义
x := []int{a, f()}
println(a, x)

// --- 示例:map 字面量中 key/value 的求值顺序未定义 ---
b := 1
g := func() int { b++; return b } // g() 会修改 b

// 若 b 先被求值:key=1, value=2  → m = {1: 2}
// 若 g() 先被执行:key=2, value=2 → m = {2: 2}
// Go 规范不保证 key 表达式与 value 表达式谁先求值
m2 := map[int]int{b: g()}
println(b, m2[b])

虽然 Go 比 C/C++ 确定得多,但在编写依赖于求值顺序的副作用代码(例如在参数列表中修改全局变量)时,依然可能会掉进“未指明行为”的陷阱。因此,最好不要在单行表达式中依赖复杂的副作用顺序。

浮点数转换的幽灵

讨论中有开发者 提到了 float64 转换为 uint8 的行为。在早期的 Go 版本中,对于溢出值的处理可能依赖于底层硬件指令(x86 vs ARM),从而表现出不一致。

虽然 Go 正在逐步收紧这些规范,例如 #76264 提案(尚未落地)正试图统一浮点转整数的饱和行为,但这提醒我们:即使是强类型语言,在跨平台移植时也可能遇到底层架构带来的“方言”差异。

如何在充满 UB 的世界里生存?

既然 Go 没有彻底消灭 UB,作为开发者,我们该如何自保?

视 -race 为生命线

Ian Lance Taylor 的警告应该被打印在每个 Go 开发者的工位上。

建议

  • 单元测试必须开启 -race 标志运行。
  • 在 CI/CD 流水线中,竞态检测是不可跳过的阻断性步骤。
  • 不要相信“我的并发逻辑很简单,不会出错”,人脑无法模拟现代 CPU 的乱序执行。

敬畏 unsafe

Go 的 unsafe 包是通往 C 语言 UB 世界的后门。使用 unsafe.Pointer 进行类型转换时,你实际上是在对编译器说:“我知道我在做什么,出了事我负责。”

除非你是编写底层运行时或极致性能库的专家,否则在业务代码中绝对禁止使用 unsafe。一旦使用,你必须熟读《Go 内存模型》和《垃圾回收器写屏障规则》。

理解“实现定义”与“未定义”的区别

  • 未定义(UB):可能导致 Crash、数据损坏、安全漏洞(如数据竞争)。零容忍。
  • 未指明/实现定义:不同版本或平台可能表现不同(如 Map 顺序)。不要依赖它。
  • 已定义:Go 承诺的行为(如整数回绕)。可以依赖,但需知晓代价。

小结:完美的幻象与工程的现实

通过这次“真相调查”,我们得出的结论可能有些令人沮丧,但也足够清醒:

Go 语言并没有彻底消灭 Undefined Behavior。它只是通过牺牲一部分性能和增加运行时检查,将 UB 的“攻击范围”从 C/C++ 的“随处可见”缩小到了“并发数据竞争”和“不安全代码”这两个特定的领域。

这是一种极其成功的工程权衡。它让 Go 在保持高性能的同时,为 99% 的日常编码提供了坚实的安全保障。

然而,作为 Gopher,我们不能沉浸在“绝对安全”的幻象中。我们必须意识到,当我们敲下 go func() 的那一刻,当我们试图共享一个指针的那一刻,我们正行走在悬崖的边缘。

Go 给了我们围栏(定义明确的行为),但也给了我们梯子(并发与 Unsafe)。能否不跌入 UB 的深渊,最终取决于我们是否遵守工程的纪律。

资料链接:https://groups.google.com/g/golang-nuts/c/MB1QmhDd_Rk


你遇到过“鼻恶魔”吗?

哪怕是 Go 这样严谨的语言,在并发面前也会露出锋利的牙齿。在你的开发生涯中,是否遇到过那种因为没开 -race 而在生产环境产生的“灵异事件”?你对 Go 这种“用性能换确定性”的哲学怎么看?

欢迎在评论区分享你的“探案”心得!


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

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

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


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

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

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

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

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


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

© 2026, bigwhite. 版权所有.

🔲 ☆

别再滥用 ClickHouse 了!单机每秒狂刷 1800 万条数据,拆解 Go+DuckDB 的“微型数仓”降维打击

本文永久链接 – https://tonybai.com/2026/03/13/go-duckdb-micro-data-warehouse-dimensionality-reduction

大家好,我是Tony Bai。

设想这样一个极其普遍的日常工作场景:

产品经理找到你,希望能给业务后台加一个“简单”的数据看板,用来实时统计用户的 PV/UV 漏斗、Nginx 日志的慢查询分析,或者是 IoT 设备的近期时序数据。

面对每天几百万到上千万条的数据量,你陷入了沉思。

如果直接用 MySQL/PostgreSQL 跑 GROUP BY 和 COUNT(DISTINCT),数据库的 CPU 瞬间飙到 100%,不仅查询要等上十几秒,甚至可能把核心交易业务一起拖死。

如果为了这个需求,去大动干戈地部署一套 ClickHouse、Elasticsearch 、Spark 集群或某个大型时序数据库……不仅运维成本上天,对于这点数据量来说,简直是用高射炮打蚊子。

在“传统关系型数据库跑不动”和“大数据集群太沉重”之间,难道就没有一个恰到好处的方案吗?

今天,我想给你介绍一个在海外工程界使用较多的方案。它不仅能把你从沉重的大数据组件中解救出来,还能在你的 Go 语言单二进制文件中,塞进一个性能恐怖的 OLAP(在线分析处理)引擎。

它就是 DuckDB。结合 Go 语言,它能在普通服务器上跑出每秒 1800 万条记录的写入速度,和毫秒级的亿级数据分析延迟。

这绝对是一场对传统数据架构的降维打击。

为什么 MySQL/PG 做不好数据分析?

很多开发者在职业生涯早期都会踩这个坑:试图用 MySQL 解决一切问题。

当你在 PostgreSQL 或 MySQL 中执行一个跨度为 30 天的聚合分析时,为什么会慢得让人绝望?因为它们的底层是“行式存储(Row-oriented)”

在行式存储中,即使你只需要 user_id 和 timestamp 这两列,数据库也必须把每一行的所有字段(包括那些庞大的 JSON 或 Text 字段)全部从磁盘加载到内存中。大量无用的 I/O 消耗,让分析查询变成了灾难。

为了解决这个问题,我们被迫引入了 ClickHouse 等“列式存储(Column-oriented)”数据库。列式存储让分析查询的速度提升了上百倍,但代价是:你需要额外部署和维护分布式集群、学习复杂的表引擎配置等。

DuckDB——OLAP 界的 SQLite

难道列式存储就必须伴随着复杂的集群部署吗?

DuckDB 给出了一个极其优雅的答案:做 OLAP 领域的 SQLite。

DuckDB 是一个纯粹的嵌入式列式数据库。它没有独立的服务器进程,而是内嵌在你的应用进程中,不需要你配置任何网络端口。它有很多语言的binding,包括Go。

在 Go 项目中,你只需要简单地 import “github.com/duckdb/duckdb-go/v2″,它就会作为动态/静态链接库,直接融入你的 Go Application 进程中。

但千万别因为“嵌入式”三个字就觉得它是玩具。社区的一款开源高性能数据库 Arc(基于 Go + DuckDB)给出了一份令人毛骨悚然的实测数据(基于MacBook Pro M3 Max (14 cores, 36GB RAM, 1TB NVMe)):

  • 写入性能:高达 18.6M+(1860万)记录/秒
  • 写入延迟:P50 < 0.5ms,P99 < 4ms
  • 查询性能:6M+(600万)行/秒扫描 (Arrow格式)

它是怎么做到的?除了列式存储,它底层还偷偷藏着两个大杀器:向量化执行引擎(Vectorized Execution) 和对 Parquet 格式的原生支持

手把手拆解 1800 万/秒的极致写入

口说无凭,我们直接上硬核源码。

很多新手刚接入 DuckDB 时,会习惯性地用标准 SQL 的 INSERT INTO … VALUES 去循环写数据。你会发现速度并不快,一秒钟只能写几万条。

真正的降维打击,藏在 DuckDB 专门为 Go 语言暴露的 Appender API 中。

Appender 绕过了繁琐的 SQL 解析器和规划器,直接将 Go 的内存数据格式,以极低的开销批量“灌”入 DuckDB 的底层列存结构中。来看这段极致狂暴的写入代码:

// https://go.dev/play/p/mHXu-kAydDX
package main

import (
    "context"
    "database/sql"
    "fmt"
    "log"
    "time"

    duckdb "github.com/duckdb/duckdb-go/v2"
)

func main() {
    // 1. 用 NewConnector 创建连接器(指定数据库文件)
    connector, err := duckdb.NewConnector("analytics.db", nil)
    if err != nil {
        log.Fatal(err)
    }
    defer connector.Close()

    // 2. 用 sql.OpenDB 打开标准 db(用于建表等 SQL 操作)
    db := sql.OpenDB(connector)
    defer db.Close()

    _, err = db.Exec(CREATE TABLE IF NOT EXISTS metrics (id INTEGER, name VARCHAR, value DOUBLE, ts TIMESTAMP))
    if err != nil {
        log.Fatal(err)
    }

    // 3. 用 connector.Connect() 获取底层 driver.Conn(Appender 需要这个)
    conn, err := connector.Connect(context.Background())
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()

    // 4. 直接传 driver.Conn,无需 Raw()
    appender, err := duckdb.NewAppenderFromConn(conn, "", "metrics")
    if err != nil {
        log.Fatal(err)
    }
    defer appender.Close()

    startTime := time.Now()

    for i := 0; i < 100000; i++ {
        err := appender.AppendRow(
            int32(i),
            fmt.Sprintf("metric_%d", i%10),
            float64(i%100),
            time.Now(),
        )
        if err != nil {
            log.Fatal(err)
        }
    }

    elapsed := time.Since(startTime)
    fmt.Printf("插入 10 万条数据耗时: %v\n", elapsed)
    fmt.Printf("吞吐量: %.0f 记录/秒\n", 100000.0/elapsed.Seconds())
}

在我的一台2019款 普通 MBP 笔记本(Intel芯片)上,上述这段代码写入 10 万条数据仅需 69 毫秒

插入 10 万条数据耗时: 69.466586ms
吞吐量: 1439541 记录/秒

换算下来,吞吐量轻松突破 143 万条/秒。如果开启并发和更大批次,逼近千万级似乎也毫无压力。这比传统的 SQL INSERT 快了整整 100 倍

替代 ELK,只需一个 Go 二进制文件

掌握了这把利器,我们该如何在实际业务中发挥它的威力?

假设你有一个 10GB 的 Nginx 日志文件(或者 CSV 文件),老板让你马上查一下昨天的 PV、UV 和慢查询排行。

过去,你需要搭建 Logstash -> Elasticsearch -> Kibana 这一套全家桶。

现在,你只需要写几十行 Go 代码。DuckDB 支持直接查询 CSV 和 Parquet 文件,连数据导入都省了

你可以直接把底层的统计逻辑嵌在你的 Go REST API 里(仅作说明使用):

// 直接在 Go 代码中,把 DuckDB 当作微型分析网关
func (adb *AnalyticsDB) GetHourlyStats() (map[string]interface{}, error) {
    // 惊人特性:直接用 SQL 语法查询本地或 S3 上的 Parquet 压缩文件!
    rows, err := adb.db.Query(
        SELECT
            DATE_TRUNC('hour', timestamp) as hour,
            COUNT(*) as pv,
            COUNT(DISTINCT path) as uv
        FROM read_parquet('s3://my-bucket/nginx_logs/*.parquet')  -- 对 Parquet 格式的原生支持与深度优化(谓词下推、列裁剪),可跳过无关数据块,大幅减少实际 I/O
        WHERE timestamp > NOW() - INTERVAL '24 hours'
        GROUP BY hour
        ORDER BY hour DESC
    )
    // ... 解析并返回给前端
}

通过这种架构,你的 Go 语言 Web 服务瞬间拥有了媲美 ClickHouse 的 OLAP 分析能力。

最绝的是,整个系统的部署产物,仅仅是一个几十 MB 的 Go 二进制文件。没有额外的依赖,丢上服务器就能跑。

小结:它不是万能的银弹

虽然 DuckDB 强到离谱,但作为高级工程师,我们必须理智看待边界。

DuckDB 绝对不适合做高并发的 OLTP(在线事务处理)。

如果你用它来扛电商的下单扣库存、或者多用户的并发更新行数据,它会死得很惨。因为它是一头为了“大吞吐分析”而生的巨兽,并没有针对行级锁和高频短事务做优化。

所以,最完美的现代架构公式应该是:

PostgreSQL/MySQL(负责核心业务流) + Go 应用内嵌 DuckDB(负责旁路日志、报表聚合的简单轻量分析)。


** 今日互动探讨:**

你在公司里遇到过哪些“为了小数据杀鸡用牛刀,强行部署大集群”的奇葩架构?或者你平时处理百万级数据分析时,最爱用什么工具?

欢迎在评论区疯狂吐槽或分享!


认知跃迁:掌控架构降维打击的底层逻辑

看到这里,你是否对日常的业务开发有了全新的视角?

在过去,面对复杂的分析需求,CRUD 程序员的本能反应是“引入一个新的重量级中间件”。

但真正的高级架构师,懂得利用底层技术栈的差异性(如行存与列存、向量化与标量计算),用最轻量、最克制的方案完成“降维打击”。

如果你的 Go 技能依然停留在写写简单的增删改查 API,对更深层的并发控制、内存管理和系统级架构选型感到迷茫——

我的极客时间专栏《Go语言进阶课》正是为你量身打造!

在这 30+ 讲硬核内容中,我将带你剥开语法糖,深入理解 Go 的底层运行机制,不仅教你写代码,更教你像顶级大厂架构师一样思考:如何用最少的组件,设计出极高并发、极低延迟的优雅系统。

目标只有一个:助你完成从“Go 熟练工”到“能做顶级架构决策的 Go 专家”的蜕变!

扫描下方二维码,加入专栏,让我们一起用技术实现“四两拨千斤”的震撼。


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

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

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


原「Gopher部落」已重装升级为「Go & AI 精进营」知识星球,快来加入星球,开启你的技术跃迁之旅吧!

我们致力于打造一个高品质的 Go 语言深度学习AI 应用探索 平台。在这里,你将获得:

  • 体系化 Go 核心进阶内容: 深入「Go原理课」、「Go进阶课」、「Go避坑课」等独家深度专栏,夯实你的 Go 内功。
  • 前沿 Go+AI 实战赋能: 紧跟时代步伐,学习「Go+AI应用实战」、「Agent开发实战课」、「Agentic软件工程课」、「Claude Code开发工作流实战课」、「OpenClaw实战分享」等,掌握 AI 时代新技能。
  • 星主 Tony Bai 亲自答疑: 遇到难题?星主第一时间为你深度解析,扫清学习障碍。
  • 高活跃 Gopher 交流圈: 与众多优秀 Gopher 分享心得、讨论技术,碰撞思想火花。
  • 独家资源与内容首发: 技术文章、课程更新、精选资源,第一时间触达。

衷心希望「Go & AI 精进营」能成为你学习、进步、交流的港湾。让我们在此相聚,享受技术精进的快乐!欢迎你的加入!

img{512x368}


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

© 2026, bigwhite. 版权所有.

🔲 ☆

你每天敲下的 go func(),藏着这位 92 岁老人的毕生心血

本文永久链接 – https://tonybai.com/2026/03/11/in-memory-of-tony-hoare

大家好,我是Tony Bai。

在这个由代码构建的现代世界里,有些名字如同星辰般指引着航向。但遗憾的是,2026 年 3 月 5 日,其中一颗最明亮的星辰熄灭了。

图灵奖得主、快速排序(Quicksort)发明者、CSP(通信顺序进程)理论之父 Tony Hoare(托尼·霍尔)与世长辞,享年 92 岁

也许你并不熟悉这个名字。但只要你是一个程序员,你就一定在面试时手写过他发明的快速排序;如果你是一个 Go 开发者,那你每天在键盘上敲下的每一个 go func() 和 make(chan int),都在调用着他留给这个世界的伟大的遗产。

今天,让我们暂时放下手头的 CRUD,跨越半个世纪的时间洪流,去看看这位非典型天才,是如何用他那近乎神迹的洞察力,赐予了 Go 语言制霸云原生时代的“并发灵魂”。

被“共享内存”支配的黑暗时代

在讲 Tony Hoare 有多伟大之前,我们必须先回忆一下,在他提出那套神级理论之前,程序员们在并发编程的泥潭里经历了怎样暗无天日的挣扎。

随着多核时代的到来,程序需要同时执行多个任务。传统的思路极其简单粗暴:共享内存(Shared Memory)。

一堆线程就像一群饿狼,死死盯着同一块内存区域。为了防止数据被写乱,程序员们被迫发明了互斥锁(Mutex)、信号量(Semaphore)。你必须极其小心地、以上帝视角去加锁、读写、释放锁。

只要你稍有不慎,忘记解锁,或者加锁顺序反了,死锁(Deadlock)和竞态条件(Race Condition) 就会像幽灵一样找上门来。程序在本地跑得好好的,一上生产环境就离奇崩溃,且极难复现、极难调试。

那是一个属于并发编程的“黑暗时代”。天下程序员苦“共享内存与锁”久矣,却找不到破局之法。

从古典哲学到“六便士的赌注”

就在整个计算机科学界在锁的泥潭里打滚时,Tony Hoare 站了出来。

有趣的是,Tony 并非科班出身。他在大学修读的竟然是古典学与哲学,后来又在皇家海军服役期间接受了高强度的俄语训练。这种看似“不务正业”的跨学科背景,赋予了他极其严密的逻辑思辨能力和哲学视角的解构能力。

他年轻时有个极其经典的轶事:在一家公司打工时,老板让他实现 Shellsort(希尔排序)。Tony 完成任务后,怯生生地对老板说:“我知道一种比这快得多的算法。” 老板不屑一顾:“我跟你赌六便士(大约几毛钱),你肯定不知道!”

于是,Tony 写出了那个后来被印在全世界每一本数据结构教材里的算法——快速排序(Quicksort)。他不仅赢走了那六便士,还顺手改变了世界。

而在面对并发编程的“绝症”时,Tony 再次展现了他哲学般的降维打击能力。

惊世骇俗的 CSP 理论

1978 年,Tony Hoare 发表了一篇名为《通信顺序进程》(Communicating Sequential Processes, 简称 CSP)的学术论文。

宛如一道闪电,这篇论文劈开了并发编程的混沌。

Tony 的哲学思维告诉他:既然共享内存那么容易出错,那我们干脆就不要共享内存了!

在 CSP 理论中,系统被划分为多个独立的、顺序执行的黑盒(进程)。它们之间没有任何共享状态。当它们需要协作时,唯一的交互方式是通过一条极其明确的管道(Channel)来“发送和接收消息”

这就像是现实生活中的流水线工人:每个人只管自己手头的活(顺序执行),做完了就通过传送带(Channel)递给下一个人。没人去抢同一个零件,自然就不需要打架(加锁)。

这种高度抽象的数学模型,完美地将复杂的并发控制,降维成了简单的数据流动。

Go 语言与云原生的基石

理论是伟大的,但在 1978 年,CSP 受限于当时的硬件架构,很难大规模工程化普及。它在学术界的象牙塔里,静静等待着一个能将它发扬光大的使者。

30 年后,谷歌的一间办公室里,Rob Pike、Ken Thompson 等几位大神正被 C++ 的并发折磨得痛不欲生。他们决定创造一门新的语言

由于 Rob Pike 早年深受 CSP 理论启发,他将 Tony Hoare 的毕生心血,直接刻进了这门新语言的基因里。这门语言,就是 Go。

Tony Hoare 论文里的晦涩数学模型,在 Go 语言里被具象化为了两个极其优雅的关键字:

  1. 顺序进程,演化成了轻量级的 Goroutine (go func())。
  2. 通信管道,演化成了强类型的 Channel (make(chan int))。

Rob Pike 更是将 CSP 的核心思想,提炼成了那句在 Go 圈子里无人不知的至理名言:

“Do not communicate by sharing memory; instead, share memory by communicating.”
(不要通过共享内存来通信,而应该通过通信来共享内存。)

让我们看一眼这被 CSP 灵魂洗礼过的代码,没有任何 sync.Mutex,没有复杂的死锁恐惧,数据的控制权随着流水的管道优雅地传递:

func main() {
    ch := make(chan int) // 创造一条 Tony Hoare 定义的通信管道

    go func() {          // 启动一个 Tony Hoare 定义的顺序进程
        ch <- 42         // 通过通信转移数据
    }()

    fmt.Println(<-ch)    // 完美接收,无需任何锁
}

Tony Hoare 也许没有预料到,他在半个世纪前写下的论文,会在今天成为支撑全球互联网的基石之一。

当我们谈论云原生时代的 Docker、Kubernetes、Prometheus 时,我们谈论的其实是 Go 语言;而当我们惊叹于 Go 语言能轻松扛起千万级的高并发调度时,我们真正应该感谢的,是底层那个名叫 CSP 的幽灵。

我们每一次扩容容器,底层的字节流都在以 Tony Hoare 所描绘的方式,有条不紊地穿梭于硅片与光纤之间。

致敬宗师:最好的纪念,是传承他的思想

Jim Miles 在追忆 Tony 的文章中提到,这位伟大的图灵奖得主极其谦逊。他曾笑着对别人说:“真正的天才不是一蹴而就的,而是在无数个日夜的深度思考中,为了一个单一问题苦苦挣扎的凡人。”

作为普通的开发者,我们无缘与这位伟人共饮下午茶,或听他亲口讲述那六便士的赌注。但作为工程师,我们对宗师最好的纪念,就是停止写那些糟糕的、充满死锁风险的并发代码,去真正理解并传承他的设计哲学。

今天,当你再次在 IDE 中敲下那个简短却充满魔力的 go func() 时,请在心底默默向这位智者致敬。

再见了,一代巨匠 Tony Hoare。

您的代码和算法已是不朽。您赐予计算世界的并发灵魂,将伴随着一代又一代的程序员,在无尽的服务器网络中,永不停止地运行下去。

参考资料

  • https://en.wikipedia.org/wiki/Communicating_sequential_processes
  • https://blog.computationalcomplexity.org/2026/03/tony-hoare-1934-2026.html

今日互动:

你在平时的 Go 开发中,是更喜欢用 Channel(CSP 模型)还是更习惯用 Mutex 锁(共享内存模型)?在并发编程中踩过哪些大坑?

欢迎在评论区分享你的心得!


认知跃迁:真正驾驭 Go 的并发灵魂

Tony Hoare 将复杂的并发问题,抽象成了极其优雅的 CSP 理论。但很多 Go 开发者,由于没有看透这层底层哲学,依然在用写 Java/C++(共享内存)的思维来写 Go,最终把 Channel 滥用得一塌糊涂,甚至引发严重的 Goroutine 泄漏。

想要真正吃透 Go 语言的并发灵魂,靠死背语法是绝对不够的。 你必须深入理解底层调度器(G-M-P 模型)是如何运作的,必须明白何时该用 Channel,何时该退回到 Mutex。

如果你渴望突破并发编程的认知瓶颈,不再只做一个“会调关键字”的熟练工,而是想成为能设计出高可用、极高并发架构的 Go 资深专家——

我的极客时间专栏 Go语言进阶课 正是为你量身定制。在这 30+ 讲硬核内容中,我将带你剥开语法糖,直击 Go 并发模型的底层骨架,重塑你的系统级架构审美。

扫描下方二维码,加入专栏。让我们用最扎实的工程实践,去向半个世纪前的伟大思想致敬!


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

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

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


原「Gopher部落」已重装升级为「Go & AI 精进营」知识星球,快来加入星球,开启你的技术跃迁之旅吧!

我们致力于打造一个高品质的 Go 语言深度学习AI 应用探索 平台。在这里,你将获得:

  • 体系化 Go 核心进阶内容: 深入「Go原理课」、「Go进阶课」、「Go避坑课」等独家深度专栏,夯实你的 Go 内功。
  • 前沿 Go+AI 实战赋能: 紧跟时代步伐,学习「Go+AI应用实战」、「Agent开发实战课」、「Agentic软件工程课」、「Claude Code开发工作流实战课」、「OpenClaw实战分享」等,掌握 AI 时代新技能。
  • 星主 Tony Bai 亲自答疑: 遇到难题?星主第一时间为你深度解析,扫清学习障碍。
  • 高活跃 Gopher 交流圈: 与众多优秀 Gopher 分享心得、讨论技术,碰撞思想火花。
  • 独家资源与内容首发: 技术文章、课程更新、精选资源,第一时间触达。

衷心希望「Go & AI 精进营」能成为你学习、进步、交流的港湾。让我们在此相聚,享受技术精进的快乐!欢迎你的加入!

img{512x368}


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

© 2026, bigwhite. 版权所有.

🔲 ☆

告别 google/uuid:Go 标准库拟新增 crypto/uuid 深度解析

本文永久链接 – https://tonybai.com/2026/03/01/goodbye-google-uuid-go-standard-library-crypto-uuid

大家好,我是Tony Bai。

在 Go 的世界里,有几个第三方库的地位几乎等同于标准库,github.com/google/uuid 绝对是其中之一。无论是微服务架构、数据库主键,还是分布式追踪,UUID 的身影无处不在。

然而,尽管其他主流语言(如 Java, C#, Python)早已将 UUID 纳入标准库,Go 却迟迟未动。直到最近,一个长达近三年讨论的提案 #62026: proposal: crypto/uuid: add API to generate and parse UUID 终于迎来了突破性进展:Go 官方提案审查委员会已将其标记为 “likely accept”(极有可能接受)。

这意味着,在不久的将来(大概率是 Go 1.27 或后续版本),我们终于可以使用官方的 crypto/uuid 包了。

不仅如此,这个issue中的数百条留言也折射出的是 Go 团队对极简主义、安全性以及现代 UUID 标准的深刻思考。

UUID 极简史:从 V1 到 V8 的演进

在深入探讨 Go 的提案之前,我们有必要先补齐 UUID(通用唯一识别码,Universally Unique Identifier)的背景知识。

UUID 是一个 128 位(16 字节)的标识符,通常以 32 个十六进制数字和 4 个连字符表示,形如:f81d4fae-7dec-11d0-a765-00a0c91e6bf6。它的核心目标是:在无需中央协调机构的情况下,保证全球范围内的唯一性。

随着技术的演进,UUID 规范(主要是 RFC 4122 以及最新的 RFC 9562)定义了多种版本,它们在生成机制上各有千秋:

  • V1 & V2 (基于时间与 MAC 地址):早期的 UUID 依赖机器的物理网卡地址和当前时间。缺点:暴露了机器身份和生成时间,存在严重隐私风险,现已极少使用。
  • V3 & V5 (基于名称的哈希):根据特定的命名空间(如 URL)和名称生成。V3 使用 MD5,V5 使用 SHA-1。相同输入永远产生相同输出。缺点:MD5 和 SHA-1 已被认为在密码学上不够安全,使用场景受限。
  • V4 (纯随机):目前最广泛使用的版本。128 位中除了 6 位用于版本和变体标识外,其余 122 位全部由密码学安全的随机数生成。优点:完全匿名,冲突概率极低。缺点:完全无序,作为数据库主键时,会导致 B+ 树索引严重碎片化,影响写入性能。
  • V6 (重新排序的 V1):为了解决 V4 的无序问题,将 V1 的时间戳字段重新排列,使其具有时间上的单调递增性。
  • V7 (时间有序的随机):新一代的王者(RFC 9562 重点推荐)。它的前 48 位是 Unix 毫秒时间戳,后面跟着充足的随机数据。优点:兼顾了 V4 的隐私性/随机性,和时间上的粗略单调递增。作为数据库主键时,插入性能远超 V4。
  • V8 (自定义):为实验性或特定供应商的格式预留。

了解了这些,我们就能理解为什么 Go 团队在设计官方 API 时,会做出一些看似“保守”的选择了。

为什么现在才引入标准库?

既然 UUID 如此重要,为什么 Go 官方拖到现在?

Go 核心成员 neild 的回答非常坦诚:

  1. 没有迫切需求:github.com/google/uuid 这个第三方库工作得非常好,API 稳定,没有不可容忍的缺陷。
  2. API 设计的迷茫:UUID 标准一直在演进。如果在 2018 年将其纳入标准库,可能只会提供 V4;而今天来看,V7 显然是必需的。由于 Go 极其严苛的向后兼容性承诺,一旦将庞杂的 API 加入标准库,就永远无法删除。

那么,为什么现在又决定引入了呢?

  • 事实上的基础设施:UUID 已经成为现代软件开发的基石。
  • RFC 9562 的发布:新的标准确立了 V7 的地位,结束了长期的混乱,是时候一锤定音了。
  • 原第三方库的维护困境:github.com/google/uuid 包含了大量历史包袱(如已废弃的方法、不再需要的错误返回等),且维护状态堪忧。Go 团队希望提供一个更精简、更现代、与 Go 核心理念更契合的官方实现。

极简的艺术:crypto/uuid API 设计解析

经过社区数月的激烈辩论,官方最终拟定的 crypto/uuid API 极度精简,展现了 Go 语言一贯的克制。

这是目前被标记为 “likely accept” 的 API 概览:

package uuid // 位于 crypto/uuid

// UUID 的本质:16个不透明的字节
type UUID [16]byte

// 变量:极值
var Nil = UUID{}
var Max = UUID{0xff, 0xff, ...} // 16个 0xff

// 构造函数
func New() UUID { return NewV4() } // 默认提供 V4
func NewV4() UUID
func NewV7() UUID

// 解析函数
func Parse(s string) (UUID, error)
func MustParse(s string) UUID

// 序列化与格式化
func (u UUID) String() string
func (u UUID) MarshalText() ([]byte, error)
func (u UUID) AppendText(b []byte) ([]byte, error)
func (u *UUID) UnmarshalText(b []byte) error

// 比较
func (u UUID) Compare(v UUID) int

乍一看,这个 API 似乎比 google/uuid 少了很多东西。这些“缺失”正是设计的精髓所在。让我们逐一解析背后的考量。

为什么底层类型是 [16]byte?

有人提议用 struct 隐藏实现,有人提议用 string。官方最终坚持使用 [16]byte。

  • 兼容性:它与 google/uuid 的底层类型完全一致,这意味着两者之间的转换仅仅是一个零成本的类型强转(Type Cast),极大降低了生态迁移的成本。
  • 语义准确:UUID 本质上就是 16 个字节的数据,没有任何序列是“非法”的。

为什么 New 函数不再返回 error?

在使用 google/uuid 时,最让人烦躁的就是 uuid.NewRandom() 会返回 (UUID, error)。因为在底层,它调用的是 crypto/rand.Read。理论上,读取系统随机数可能会失败。

但现实中,现代操作系统的安全随机源(如 /dev/urandom 或 getrandom 系统调用)几乎不可能失败。如果它失败了,说明你的系统内核已经崩溃,此时程序 panic 才是最正确的选择。

Go 1.24 引入的 #66821 提案明确了 crypto/rand 会在失败时直接致命退出(Fatal)。因此,在新的 crypto/uuid 中,所有的 New 函数都去掉了冗余的 error 返回值,极大地净化了调用方的代码。

// 以前
id, err := uuid.NewRandom()
if err != nil { ... }

// 现在
id := uuid.New() // 爽!

为什么只提供 V4 和 V7?V1、V3、V5 呢?

Go 安全团队负责人 Roland Shoemaker 对开源生态进行了大规模的数据挖掘,发现:

  • 超过 90% 的调用是生成随机 UUID(V4)。
  • 生成 V5 的函数(NewSHA1)使用率不足 0.05%

基于“如无必要,勿增实体”的原则,官方决定只提供 V4 和 V7。

  • NewV4:当你只需要一个纯随机的唯一标识符时。
  • NewV7:当你的标识符会被用作数据库主键,且你希望获得更好的插入性能(时间局部性)时。

如果你真的需要 V5 这种基于 SHA-1 的弱哈希 UUID 怎么办?社区的回答是:自己写,或者继续用第三方库。标准库不应该为这种罕见且安全性存疑的场景买单。

V7 的争议:要不要提供时间偏移量(Offset)?

这是提案中最激烈的交锋之一。

一些数据库专家强烈要求提供类似 NewV7WithOffset(offset) 的方法。他们认为,在极高并发的分布式数据库中,完全连续的时间戳会导致 B 树索引的写入热点(Hotspot)。通过稍微偏移时间戳,可以打散写入压力。同时,偏移也能隐藏真实的创建时间,保护隐私。

然而,Go 核心团队(neild)坚决拒绝了这个提议:

  • 偏离规范:RFC 9562 的初衷就是利用时间局部性。如果你故意打乱时间,那为什么要用 V7?不如直接用 V4。
  • 隐私悖论:如果你担心泄露创建时间,V7 本身就不是正确的选择。
  • 增加复杂性:这属于极少数高级数据库引擎才会考虑的特性,不应该污染基础库的通用 API。

为什么没有 Version() 和 Time() 等解析方法?

在 google/uuid 中,你可以通过方法提取 UUID 的版本号或时间戳。但在新标准库中,这些被全部砍掉。

原因:遵循 RFC 9562 的“不透明性”(Opacity)原则。规范明确指出:“建议尽可能将 UUID 视为不透明(Opaque)的值,除非绝对必要,应避免解析 UUID。”

UUID 是用来比较和标识的,不是用来承载业务逻辑的。如果你试图从 UUID 中提取时间,并依此执行业务判断,那么你的架构设计大概率出了问题。

数据库集成与生态迁移

对于 Gopher 来说,UUID 最常见的作用就是存入数据库。

google/uuid 之所以流行,很大程度上是因为它实现了 database/sql/driver.Valuer 和 sql.Scanner 接口,可以无缝与各种 ORM(如 GORM)和数据库驱动配合。

令人惊讶的是,新的 crypto/uuid 并没有实现这些接口。

这是因为 Go 团队认为方向搞反了。 不应该是底层的 crypto 库去依赖 database/sql,而应该是 database/sql 原生认识 UUID 这种基础类型。

目前的计划是,与 crypto/uuid 同步,修改 database/sql 和底层驱动框架,使其在遇到 uuid.UUID 类型时,自动完成与字符串(或字节)的转换。这种解耦设计更加优雅。

小结

crypto/uuid 的提案,表面上只是增加了一个小小的包,实则又是一场关于 Go 工程哲学的集中展示:

  1. 极度克制:砍掉 99% 开发者不需要的 80% 的功能(V1-V3, V5, 提取内部信息),只保留最核心的骨架(V4, V7, 解析, 格式化)。
  2. 安全性优先:放在 crypto 目录下,强调其依赖密码学安全的随机数;拒绝支持已被攻破的弱哈希算法(MD5/SHA1)。
  3. 零冗余处理:借助语言底层的进化(crypto/rand 必定成功),去掉了无意义的 error 返回。

对于我们普通的 Go 开发者来说,未来的迁移路径将非常简单:

当 Go 版本更新后,我们只需要将 import 路径从 github.com/google/uuid 替换为 crypto/uuid。由于底层类型都是 [16]byte,甚至不用担心性能下降。

告别那些臃肿的、历史包袱沉重的第三方库,拥抱一个清爽、安全、原生的 crypto/uuid,Gopher 们,准备好了吗?

资料链接:https://github.com/golang/go/issues/62026


你会第一时间切换吗?

面对即将到来的原生 crypto/uuid,你是支持“极简主义”的官方版本,还是离不开功能丰富的 google/uuid?在你的项目中,UUID V7 的单调递增特性是否真的解决了数据库索引碎片的问题?

欢迎在评论区分享你的看法,我们一起坐等 Go 1.27!


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

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

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


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

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

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

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

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


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

© 2026, bigwhite. 版权所有.

🔲 ☆

Android Perfetto 系列 10 - Binder 调度与锁竞争

本文是 Perfetto 系列的第十篇文章,聚焦 Binder 这一 Android 跨进程通信的核心机制。Binder 承载着大部分系统服务与应用的交互,也常常是性能瓶颈的源头。本文站在系统开发与性能调优的视角,结合 linux.ftrace(binder tracepoints + sched)、thread_state 轨道,以及 ART 的 Java Monitor Contention(通过 atrace 的 dalvik 类别采集)等信号,给出一套可直接落地的诊断流程,帮助初学者和进阶开发者定位耗时、线程池压力与锁竞争等问题。

本系列的目标,就是通过 Perfetto 这个工具,从一个全新的图形化视角,来审视 Android 系统的整体运行,同时也提供一个学习 Framework 的新途径。或许你已经读过很多源码分析的文章,但总是对繁杂的调用链感到困惑,或者记不住具体的执行流程。那么通过 Perfetto,将这些流程可视化,你可能会对系统有更深入、更直观的理解。

本文目录

Perfetto 系列文章

  1. Android Perfetto 系列目录
  2. Android Perfetto 系列 1:Perfetto 工具简介
  3. Android Perfetto 系列 2:Perfetto Trace 抓取
  4. Android Perfetto 系列 3:熟悉 Perfetto View
  5. Android Perfetto 系列 4:使用命令行在本地打开超大 Trace
  6. Android Perfetto 系列 5:Android App 基于 Choreographer 的渲染流程
  7. Android Perfetto 系列 6:为什么是 120Hz?高刷新率的优势与挑战
  8. Android Perfetto 系列 7 - MainThread 和 RenderThread 解读
  9. Android Perfetto 系列 8:深入理解 Vsync 机制与性能分析
  10. Android Perfetto 系列 9 - CPU 信息解读
  11. Android Perfetto 系列 10 - Binder 调度与锁竞争
  12. 视频(B站) - Android Perfetto 基础和案例分享
  13. 视频(B站) - Android Perfetto 分享 - 出图类型分享:AOSP、WebView、Flutter + OEM 系统优化分享

Binder 基础与案例

对于首次接触 Binder 的读者,理解它的角色和参与者至关重要。可以先把 Binder 粗暴地理解成“跨进程的函数调用”:你在一个进程里像调用本地接口一样写代码,真正的调用和数据传输则由 Binder 帮你完成。整体上它是 Android 的主力跨进程通信(IPC)机制,核心包含四个组件:

  1. Client:应用线程通过 IBinder.transact() 发起调用,将 Parcel 序列化的数据写入内核。
  2. Service(Server):通常运行在 SystemServer 或其他进程中,通过 Binder.onTransact() 读取 Parcel 并执行业务逻辑。
  3. Binder Driver:内核模块 /dev/binder 负责线程池调度、缓冲区管理、优先级继承等,是连接双方的“信使”。
  4. Thread Pool:服务端通常维护一组 Binder 线程。需要注意的是,线程池并不是一开始就创建满的,而是按需创建。Java 层默认最大线程数约为 15 个 Binder 工作线程(不含主线程),Native 层通过 ProcessState 也可以配置最大线程数(默认值通常也是 15)。当所有 Binder 线程都忙碌时,新的请求就会在驱动层排队等待空闲线程。

为什么需要 Binder?

Android 采用多进程架构来隔离应用、提升安全性与稳定性。每个 APK 运行在独立的用户空间,当需要访问系统能力(相机、位置、通知等)时,必须跨进程调用 Framework 或 SystemServer。

传统 IPC 方案的局限:

IPC 方式问题
Socket开销大,缺少身份校验
Pipe仅支持父子进程,单向通信
共享内存需要额外的同步机制,缺少访问控制

Binder 在内核层解决了这些问题,提供了三个关键能力:一是身份与权限(基于 UID/PID 校验,确保调用方合法);二是同步与异步调用(同步模式下 Client 等待 Server 返回,这是最常见的模式,而异步模式下 Client 发送后立即返回,适用于通知、状态上报等场景);三是优先级继承(当高优先级 Client 调用低优先级 Server 时,Server 会临时提升优先级,避免优先级反转问题)。

因此,当应用进程在启动阶段通过 IActivityManager#attachApplication() 把自己“挂到” SystemServer 时,底层必然借助 Binder 把调用安全、可靠地传递给 system_server

从 App 开发者视角的案例

假设我们在 Trace 里关注到 AIDL::java::IActivityManager::attachApplication::server。它对应的是应用进程通过 IActivityManager#attachApplication(...) 发起的一次同步 Binder 调用,服务端实现位于 system_serverActivityManagerService。调用路径可以概括为:首先,在 Proxy 侧,应用进程通过 ActivityManager.getService() 拿到一个 IActivityManager 的代理对象(BinderProxy);然后进行序列化,调用 attachApplication(...) 时,代理会把参数写入 Parcel,执行 transact();接着是内核传输,Binder 驱动将该事务排入 system_server 的 Binder 线程队列,并唤醒一个空闲线程(例如 Binder:1460_5);随后在 Stub 侧ActivityManagerService(Stub)所在的线程被唤醒,读取参数并进入 attachApplication 的处理流程;最后是返回阶段,Service 处理完毕,将结果写入 Parcel,驱动唤醒原 App 线程,App 线程从 waitForResponse() 返回继续执行。

在 Perfetto 中,这条链路会显示为:Android Binder / Transactions 轨道上的一次事务(如果 trace 中能解析到 AIDL 信息,Slice 名称会类似 AIDL::java::IActivityManager::attachApplication::client/server,或在 SQL 中体现为 aidl_name=IActivityManagermethod_name=attachApplication);App 线程在 thread_state 里处于 S (Sleeping) 状态(同步调用时常见),且 blocked_function 通常涉及 binder_thread_read / epoll_wait / ioctl(BINDER_WRITE_READ);SystemServer 的 Binder 线程出现 Running 切片;以及 Flow 箭头(Perfetto 会用箭头把 Client 的 transact 和 Server 的处理线程连接起来)。

image-20260207113358540

Perfetto 观测准备

要在 Perfetto 中诊断 Binder,需要提前准备好数据源与 Trace 配置。

数据源与轨道总览

Binder 分析需要把「事务事件」和「线程调度/阻塞/锁」串起来。录制侧主要依赖 linux.ftrace(包含 Binder tracepoints、调度事件以及可选的 atrace 类别),再配合少量元数据源(进程/线程命名映射)。

linux.ftrace(内核层 + atrace) 是最通用、最基础的数据源,兼容所有 Android 版本。它直接读取内核的 ftrace 事件,包括 binder_transaction(事务开始)、binder_transaction_received(服务端收到事务)、binder_transaction_alloc_buf(缓冲区分配,诊断 TransactionTooLarge)等;再配合调度相关事件(sched_switchsched_waking)即可还原出 “Client 发起调用 → 内核唤醒 Server 线程 → Server 处理 → 返回” 的链路。
另外,linux.ftrace 里还可以开启 atrace 类别来补充用户态 Slice:binder_driver/am/wm 等有助于解释系统服务语义;dalvik 则用于采集 ART 的 monitor contention(Java synchronized 竞争),从而在 UI 里出现 Thread / Lock contention 相关轨道。

linux.process_stats(元数据) 用于把 PID/TID 映射成进程名/线程名,方便在 UI 和 SQL 中阅读与过滤。开销极低,建议常开。

说明:Perfetto UI 中的 Android Binder / TransactionsAndroid Binder / Oneway Calls 轨道,以及 PerfettoSQL 标准库中的 android.binder / android.monitor_contention 模块,都是在 trace processor 侧基于上述原始事件解析/聚合出来的,并不是需要额外开启的“录制数据源”。

Trace Config 推荐

以下配置兼顾了兼容性与新特性,建议作为标准的 Binder 分析模板。将配置保存为 binder_config.pbtx 即可使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# ============================================================
# Binder 分析专用 Perfetto 配置
# 适用范围:Android 10+(Android 12+ 在路径/权限上更省心)
# ============================================================

# --- 缓冲区与时长设置 ---
buffers {
size_kb: 65536 # 64MB 缓冲区,适合中等复杂度场景
fill_policy: RING_BUFFER
}
duration_ms: 15000 # 15 秒抓取时长,可根据需要调整

# --- 数据源 1:linux.ftrace (内核 + atrace) ---
# 最通用的数据源:Binder tracepoints + sched 事件 +(可选)atrace 类别
data_sources {
config {
name: "linux.ftrace"
ftrace_config {
# Binder 核心事件
ftrace_events: "binder/binder_transaction" # 事务开始
ftrace_events: "binder/binder_transaction_received" # 服务端收到事务
ftrace_events: "binder/binder_transaction_alloc_buf" # 缓冲区分配(诊断 TransactionTooLarge)
ftrace_events: "binder/binder_set_priority" # 优先级继承
ftrace_events: "binder/binder_lock" # 内核锁(通常可省略)
ftrace_events: "binder/binder_locked"
ftrace_events: "binder/binder_unlock"

# 调度事件(串联 Client/Server 线程)
ftrace_events: "sched/sched_switch"
ftrace_events: "sched/sched_waking"
ftrace_events: "sched/sched_wakeup"
ftrace_events: "sched/sched_blocked_reason" # 阻塞原因

# 可选:应用层 Trace 点(需要 atrace)
atrace_categories: "binder_driver" # Binder 驱动层
atrace_categories: "sched" # 调度
atrace_categories: "am" # ActivityManager
atrace_categories: "wm" # WindowManager
atrace_categories: "dalvik" # Java Monitor Contention(锁竞争)
# atrace_categories: "view" # 如需分析 UI 可开启

# 如需抓取应用侧 atrace Slice(例如 doFrame / 自定义 Trace),可指定:
# atrace_apps: "你的应用包名"
# 或者抓全部(Trace 体积会变大):
# atrace_apps: "*"

# 符号化内核调用栈
symbolize_ksyms: true

# 优化调度事件存储,减少 Trace 体积
compact_sched {
enabled: true
}
}
}
}

# --- 数据源 2:linux.process_stats (进程信息) ---
# 提供进程名、PID 等基础信息
data_sources {
config {
name: "linux.process_stats"
process_stats_config {
scan_all_processes_on_start: true
}
}
}

配置项说明

数据源作用Android 版本要求开销
linux.ftrace (binder/*)内核层 Binder 事件所有版本
linux.ftrace (sched/*)调度事件,串联线程唤醒所有版本
linux.ftrace (atrace: dalvik/…)Framework Slice + Java Monitor Contention所有版本(字段随版本演进)低-中
linux.process_stats进程名称和 PID 映射所有版本极低

提示:本文的 Binder 分析工作流只依赖 linux.ftrace(binder tracepoints + sched + dalvik),因此 Android 12/13/14+ 的抓取思路基本一致。不同版本的 UI 字段名可能略有差异,遇到差异时推荐用 Perfetto SQL(stdlib)做校验。

快速上手:3 步抓取与查看 Binder Trace

  1. 抓取 Trace

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 推送配置
    adb push binder_config.pbtx /data/local/tmp/

    # 开始抓取
    adb shell perfetto --txt -c /data/local/tmp/binder_config.pbtx \
    -o /data/misc/perfetto-traces/trace.pftrace

    # ... 操作手机复现卡顿 ...

    # 取出文件
    adb pull /data/misc/perfetto-traces/trace.pftrace .
  2. 打开 Trace:访问 ui.perfetto.dev,拖入 trace 文件。

  3. 添加关键视图

    • 左侧点击 TracksAdd new track
    • 搜索 “Binder”,添加 Android Binder / TransactionsAndroid Binder / Oneway Calls
    • 搜索 “Lock”,添加 Thread / Lock contention(如果有数据)

其他 Binder 分析工具

除了 Perfetto,还可以用两个工具辅助定位:am trace-ipc(系统自带)和 binder-trace(开源,能力更强但门槛更高)。

am trace-ipc:Java 层 Binder 调用追踪

am trace-ipc 用于追踪 Java 层 Binder 调用堆栈。系统会在目标进程开启 Binder stack tracking(BinderProxy.transact() 路径),在停止时导出文本统计。优点是零配置、无需 root。

基本用法很简单,就是”开始 → 操作 → 停止导出”三步:

1
2
3
4
5
6
7
8
9
10
# 1. 开始追踪(系统会记录符合条件进程的 Binder 调用,通常以可调试进程为主)
adb shell am trace-ipc start

# 2. 在手机上执行你要分析的操作(比如启动某个应用、触发卡顿场景等)

# 3. 停止追踪并导出结果到文件
adb shell am trace-ipc stop --dump-file /data/local/tmp/ipc-trace.txt

# 4. 把结果文件拉到电脑上查看
adb pull /data/local/tmp/ipc-trace.txt

导出结果是纯文本,示例如下:

1
2
3
4
5
6
7
Traces for process: com.example.app
Count: 15
java.lang.Throwable
at android.os.BinderProxy.transact(BinderProxy.java:xxx)
at android.app.IActivityManager$Stub$Proxy.startActivity(...)
at android.app.Instrumentation.execStartActivity(...)
...

它会按进程分组,列出调用堆栈和次数(Count),适合快速回答“调了哪些服务、调了多少次”。

与 Perfetto 配合使用:Perfetto 看时间线与线程关系,trace-ipc 补“具体是哪个 Java 调用点发起调用”。

适用场景:怀疑卡顿/ANR 与频繁 IPC 有关,或需要定位具体 Java 发起点。

binder-trace:实时 Binder 消息解析

binder-trace 可以实时拦截并解析 Binder 消息,常被称为“Binder 的 Wireshark”,能看到接口、方法及部分参数。

它基于 Frida 动态注入,通常需要 root(或模拟器)和 frida-server,本地需 Python 3.9+。示例:

1
2
# 追踪指定应用的 Binder 通信(-d 指定设备,-n 指定进程名,-a 指定 Android 版本)
binder-trace -d emulator-5554 -n com.example.app -a 11

它支持按接口/方法/事务类型过滤,适合安全研究和逆向分析这类“看消息内容”的场景。日常性能排查通常仍以 Perfetto + am trace-ipc 为主。

Binder 分析工作流

拿到 Trace 后,不要直接在大海捞针。推荐按照“找目标 → 看耗时 → 查线程 → 找锁”的顺序进行。

步骤一:定位事务耗时

分析的第一步是找到你关心的那次 Binder 调用。在 Perfetto 中有几种常用的定位方式:如果你已经知道是哪个进程发起的调用,可以直接在 Transactions 轨道里找到你的 App 进程作为 Client 的区域;如果你知道调用的接口名或方法名,可以按 / 键打开搜索框,输入 AIDL 接口名(如 IActivityManager)、方法名(如 attachApplication),或者直接输入完整的 Slice 名(如 AIDL::java::IActivityManager::attachApplication::server)来快速定位;如果你是在排查 UI 卡顿问题,最直接的方式是先看 UI 线程的 thread_state 轨道,找到处于 S(Sleeping)状态且时长较长的片段——如果这段时间主线程几乎没有在执行代码,那很可能就是在等待 Binder 调用返回,这里就是分析的起点。

选中一个 Transaction Slice 后,右侧的 Details 面板会显示这次事务的详细信息(Client/Server 线程、时间戳、耗时等)。不同版本的 Perfetto UI 字段名可能略有差异,但你可以用 Perfetto SQL 的 android_binder_txns 来统一理解几个关键耗时:

  • client_dur:客户端端到端耗时(同步调用时基本等同于“我在等这次 Binder 返回”的时间)
  • server_dur:服务端从开始处理到(同步时)发出 reply 的 wall clock 时长
  • dispatch_dur = server_ts - client_ts:从客户端发起到服务端真正开始处理的延迟(常包含排队/线程可用性/调度影响)

下面这段 SQL 可以直接在 Perfetto UI 的 SQL 页面运行(用于快速找出最慢的同步事务,并拆出派发延迟与服务端耗时):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
INCLUDE PERFETTO MODULE android.binder;

SELECT
aidl_name,
method_name,
client_process,
client_thread,
client_dur / 1e6 AS client_ms,
server_process,
server_thread,
server_dur / 1e6 AS server_ms,
(server_ts - client_ts) / 1e6 AS dispatch_ms
FROM android_binder_txns
WHERE is_sync
ORDER BY client_dur DESC
LIMIT 20;

image-20260207110047680

理解这些耗时之间的关系非常重要,因为它直接决定了你下一步应该往哪个方向深挖。如果 client_dur 很长但 server_dur 很短,通常说明慢主要不在服务端处理,而是在派发/排队(dispatch_dur 会很大),这时应该优先检查服务端线程池与调度情况(步骤二)。如果 server_dur 本身就很长,说明服务端处理慢,这时你需要跳转到服务端的 Binder 线程,看它在这段时间里到底在干什么——是在跑业务代码、等锁、还是等 IO。

步骤二:评估线程池与 Oneway 队列

如果步骤一的分析发现耗时主要不在服务端处理,而是在”排队”上,那就需要进一步检查 Binder 线程池的状态了。在深入分析之前,先回答一个经常被问到的问题:**”每个进程大概会有多少个 Binder 线程?system_server 的 Binder 线程池规模大致是什么量级?什么情况下会’耗尽’?”**

system_server 的 Binder 线程池规模

在上游 AOSP(Android 14/15)中,Binder 线程池的设计思路是:按需增长、可配置、没有单一固定数字

  • 线程池是按需增长的:每个服务端进程在 Binder 驱动中维护一个线程池,实际线程数会根据负载按需增减,上限由内核中的 max_threads 字段和用户态 ProcessState#setThreadPoolMaxThreadCount() 等配置共同决定。
  • 典型上限取决于进程角色:普通应用进程的 Binder 工作线程上限通常在 15 左右(libbinder 默认值);但 system_server 会在启动时显式把上限调高,AOSP 当前代码中设置为 31。因此,system_server 并不等同于“默认十几条线程”。
    某些厂商 ROM 或定制内核会根据自身负载模型,把上限调大或调小(例如调到几十条线程),因此你在不同设备上通过 ps -T system_servertop -H 或 Perfetto 数 Binder: 线程时,看到的具体数字可能会有差异。
  • 以实际观测为准,而不是死记一个数字:在 Perfetto 里,更推荐的做法是直接展开某个进程,看有多少个 Binder:xxx_y 线程轨道,以及它们在抓 Trace 期间的活跃程度,以此来评估线程池的“规模”和“繁忙度”。

Binder 线程数、缓冲区与“Binder 耗尽”

在性能分析中,大家提到“Binder 个数”时,往往会混在一起谈三类不同的资源限制:

Binder 线程池耗尽是指某个进程内所有 Binder 工作线程都处于 Running / D / S 等忙碌状态,没有空闲线程可以被驱动唤醒处理新事务。其现象包括 Client 线程在 thread_state 轨道里长时间停留在 S 状态(调用栈停在 ioctl(BINDER_WRITE_READ) / epoll_wait),并且在 SQL 里可以观察到大量事务的 dispatch_durserver_ts - client_ts)显著偏大——说明请求在服务端真正开始处理之前就已经卡在“等线程/等调度”上了。对于 system_server 这类关键进程,线程池被打满意味着系统服务响应能力下降,很容易放大为全局卡顿或 ANR

Binder 事务缓冲区耗尽涉及每个进程在 Binder 驱动里的一块有限大小的共享缓冲区(典型值约 1MB 量级),用于承载正在传输的 Parcel 数据。典型场景包括一次事务传输过大的对象(如大 Bitmap、超长字符串、大数组等),以及大量并发事务尚未被消费完,导致缓冲区中堆积了太多尚未释放的 Parcel。可能的结果包括内核日志中出现 binder_transaction_alloc_buf 失败、Java 层抛出 TransactionTooLargeException,以及后续事务在驱动层长时间排队甚至失败(看起来像是“Binder 被用光了”)。解决这类问题的思路不是通过“多开线程”,而是控制单次传输的数据量(拆包、分页、流式协议),并对大块数据优先使用 SharedMemory / 文件 / ParcelFileDescriptor 等机制。

Binder 引用表 / 对象数量方面,Binder 驱动会为每个进程维护引用表和节点对象,这些也有上限,但在大多数实际场景中,很少首先撞到这里。常见风险是长时间持有大量 Binder 引用却不释放,更多体现为内存/稳定性问题,而不是 UI 卡顿。

在 Perfetto 里分析时,可以带着一个判断框架:
“现在的慢,是因为线程池被打满,还是事务过大/缓冲区被用光?”
前者主要看 **Binder 线程数与它们的 thread_state**,以及事务的 dispatch_durserver_ts - client_ts,可近似理解为派发/排队延迟);后者则关注 单次事务的大小、并发事务数量和是否伴随 TransactionTooLargeException / binder_transaction_alloc_buf 相关日志


现在回到我们的分析场景:

Binder 线程池的繁忙程度直接决定了服务的并发处理能力。对于同步事务来说,如果服务端 Binder 线程长期处于 RunningUninterruptible Sleep (D) 状态,新的请求就会在内核里排队,客户端线程会长时间阻塞在 ioctl(BINDER_WRITE_READ) / epoll_wait,主线程在 thread_state 上通常表现为长段 S(Sleeping)。

在 Perfetto 中诊断线程池问题,优先看两个信号:Binder 线程是否长期满载,以及事务的 **dispatch_dur 是否显著大于 server_dur**(判读方式与步骤一一致)。

关于 Oneway 调用在 Perfetto 中的识别:同步调用(Two-way)和异步调用(Oneway)在 Perfetto 中的表现有明显区别,学会区分它们对分析很有帮助。同步调用时,客户端会阻塞等待(thread_state 显示 S),Perfetto 通常会画出 transaction → reply 的 Flow;而 Oneway 调用客户端发完就返回、几乎无阻塞,Flow 只有单向的 transaction,没有 reply 回来。另外,Oneway 调用的 Slice 名称后面可能会带 [oneway] 标记;在 SQL 里也可以通过 android_binder_txns.is_sync = 0 来过滤 Oneway。

在分析 Oneway 相关问题时,重点关注两件事:一是服务端的队列深度(如果同一 IBinder 对象上的 Oneway 请求堆积,后续请求的实际执行时机会被不断延后);二是是否存在批量发送的模式(短时间内大量 Oneway 调用会形成”尖峰”,在 Perfetto 中表现为服务端 Binder 线程上密集排列的短 Slice)。

image-20260207130758344

值得一提的是,SystemServer 的 Binder 线程不仅要处理来自各个 App 的请求,还要处理系统内部的调用(比如 AMS 调 WMS、WMS 调 SurfaceFlinger 等)。如果某个”行为不端”的 App 在短时间内疯狂发送 Oneway 请求,可能会把某个系统服务的 Oneway 队列塞满,进而影响到其他 App 的异步回调时延,造成全局性的卡顿感。

步骤三:排查锁竞争

如果你跳转到服务端的 Binder 线程,发现它在处理你的请求期间长时间处于 S(Sleeping)或 D(Disk Sleep / Uninterruptible Sleep)状态,那通常意味着它在等待某个资源——要么是在等锁,要么是在等 IO。锁竞争是 SystemServer 中非常常见的性能瓶颈来源,因为 SystemServer 里运行着大量服务,它们之间共享很多全局状态,而这些状态往往通过 synchronized 锁来保护。

Java 锁(Monitor Contention) 是最常见的情况。SystemServer 中有不少全局锁,比如 WindowManagerService 的 mGlobalLock、ActivityManagerService 的一些内部锁等。当多个线程同时需要访问被这些锁保护的资源时,就会产生竞争。在 Perfetto 中,如果你看到某个 Binder 线程状态为 S,并且 blocked_function 字段包含 futex 相关的符号(如 futex_wait),那基本可以确定是在等 Java 锁。要进一步确认是在等哪个锁、被谁持有,可以查看 Lock contention 轨道。Perfetto 会把锁竞争的关系可视化出来:用连接线标出 Owner(持有锁的线程,比如 android.display 线程)和 Waiter(等待锁的线程,比如处理你请求的 Binder:123_1)。点击 Contention Slice,还可以在 Details 面板里看到锁对象的类名(比如 com.android.server.wm.WindowManagerGlobalLock),这对于理解问题的根源非常有帮助。

image-20260207130953951

Native 锁(Mutex / RwLock) 的情况相对少见一些,但在某些场景下也会遇到。表现形式类似:线程状态为 DS,但调用栈里出现的是 __mutex_lockpthread_mutex_lockrwsem 等 Native 层的符号,而不是 Java 的 futex_wait。分析这类问题通常需要结合 sched_blocked_reason 事件来看线程具体在等什么,属于比较进阶的内容,这里就不展开了。

使用 SQL 统计 system_server 中的 Java Monitor Contention(可选)

PerfettoSQL 标准库已经提供了解析后的 android_monitor_contention 表(由 ART 的 monitor contention 相关 Slice 解析而来),建议优先使用它来做统计,而不是手工解析 slice 名称字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
INCLUDE PERFETTO MODULE android.monitor_contention;

SELECT
process_name,
blocked_thread_name AS waiter_thread,
blocking_thread_name AS owner_thread,
(dur / 1e6) AS dur_ms,
(waiter_count + 1) AS waiter_threads,
short_blocked_method,
short_blocking_method,
blocked_src,
blocking_src
FROM android_monitor_contention
WHERE process_name = 'system_server'
ORDER BY dur DESC
LIMIT 50;

提示:如果查不到数据,请确认抓取时 atrace_categories 包含 dalvik,并且问题场景中确实发生了 monitor contention。

image-20260207105941270

最新平台特性与优化建议

随着 Android 版本演进,Binder 在性能与稳定性上也持续增强。理解这些机制有助于解释 Perfetto 现象并指导优化。

Binder Freeze(Android 12+):Cached 进程被冻结后几乎不获得 CPU。对其发起同步 Binder 调用会被拒绝,并可能触发目标进程终止;异步(oneway)事务通常先缓冲,待解冻后处理。

Frozen-callee 回调策略(Android 14+ 常见):可用 RemoteCallbackList 的 policy(DROPENQUEUE_MOST_RECENTENQUEUE_ALL)控制冻结期间回调堆积,降低解冻后的抖动与压力。

Binder Heavy Hitter Watcher:用于识别短时间内占比异常高的 Binder 调用热点。启用方式、阈值和输出渠道依版本与设备配置而定。

给开发者的一些建议

关于 Oneway:只在确实不需要返回值和完成时机时使用(如日志、状态通知)。把同步调用硬改成 Oneway 往往只会把等待转移到服务端队列,并引入时序问题。

关于 大数据传输:避免直接走 Binder(尤其是 Bitmap)。单进程 Binder 缓冲区约 1MB,容易触发 TransactionTooLargeException;应改用 SharedMemory、文件或 ParcelFileDescriptor

关于 主线程调用:不要在 UI 线程调用耗时不可控的 Binder 服务;若必须调用,请放到后台线程,完成后再回主线程更新 UI。

总结

Perfetto 是分析 Binder 问题的高效工具。核心方法是:用 linux.ftrace 抓取 binder/sched/dalvik 信号,在 UI 中沿 Flow 串联 Client 与 Server,再结合 client_dur / server_dur / dispatch_dur、线程状态和锁竞争,区分“排队慢”“处理慢”“等锁”。

遇到难解释的 UI 卡顿或 ANR 时,可按“主线程是否在等 Binder → 服务端是否排队/处理慢/等锁”的顺序排查。再结合 CPU、调度、渲染等信号,通常能更快定位根因。

参考

  1. 理解Android Binder机制1/3:驱动篇
  2. PerfettoSQL stdlib - android.binder
  3. Perfetto Documentation - Ftrace
  4. Android Source - Binder
  5. Android Developers - Parcel and Bundle
  6. binder-trace - Wireshark for Binder
  7. am trace-ipc 源码分析

*

关于我 && 博客

  1. 博主个人介绍
  2. 本博客内容导航
  3. Android性能优化知识星球

一个人可以走的更快 , 一群人可以走的更远

微信扫一扫

🔲 ☆

大项目构建太慢?Brad Fitzpatrick 提议引入 -cachelink 降低测试等待时间

本文永久链接 – https://tonybai.com/2026/02/05/brad-fitzpatrick-cachelink-reduce-go-test-wait-time

大家好,我是Tony Bai。

在维护大型 Go 单体仓库(Monorepo)时,你是否遇到过这样的场景:明明只是修改了测试的运行参数(比如 -run 的正则),或者在不同的 CI 节点上运行同一个包的测试,却发现 go test 依然在缓慢地执行“链接(Linking)”步骤?

对于代码量巨大的项目,链接过程往往是构建链条中最耗时的一环。为了解决这一痛点,Go 社区领袖、Tailscale 核心开发者 Brad Fitzpatrick 近日提交了 #77349 提案,建议引入 -cachelink 标志。这一看似微小的改动,有望在分布式测试和重复执行场景下,显著“挤出”原本被浪费的等待时间。

被忽视的瓶颈:重复链接的代价

Go 的构建缓存(GOCACHE)机制已经非常高效,它能很好地缓存编译阶段的中间产物(.a 文件)。但是,当你运行 go test 时,工具链的最后一步——将所有依赖链接成一个可执行的测试二进制文件——通常是“一次性”的。

这意味着,即使你的代码没有任何变动,只要测试指令稍有变化(例如多次运行 go test 但指定不同的测试用例),Go 工具链往往会重新触发链接器。

# 第一次运行:链接 + 执行
$ go test -run=^TestFoo$ ./pkg/

# 第二次运行(代码未变):依然触发重新链接 + 执行
$ go test -run=^TestBar$ ./pkg/

对于依赖项数以千计的大型项目,链接过程可能长达数秒甚至更久。在本地频繁调试或 CI 流水线中,这些重复的秒数累积起来就是巨大的时间浪费。

Brad 的解法:-cachelink

Brad Fitzpatrick 的提案非常直接:允许将链接器输出的最终测试二进制文件,也写入 GOCACHE。

通过显式开启 -cachelink,go test 的行为将发生变化:

  1. 它会基于构建输入(代码、依赖、环境变量等)计算哈希。
  2. 如果发现 GOCACHE 中已经存在已链接好的测试二进制文件。
  3. 直接跳过链接步骤,复用该文件进行测试。

这样,上述例子中的第二次调用将瞬间启动,因为最耗时的构建步骤被完全省去了。

为什么不做成默认行为?

既然能提速,为什么不默认开启?Brad 在提案讨论中给出了专业的权衡分析:

空间 vs. 时间

测试二进制文件通常包含完整的符号表和调试信息,体积比普通的中间对象文件大得多。如果默认缓存所有测试二进制文件,开发者的磁盘空间(GOCACHE)会迅速膨胀。因此,这是一个以空间换时间的策略,更适合由开发者根据项目规模手动开启,或者在 CI 环境中配置。

分布式 CI 的“加速器”

该提案真正的杀手级应用场景是 分布式 CI 系统。

许多大厂使用 GOCACHEPROG 来在构建集群间共享缓存。在典型的 CI 流程中,测试任务往往会被分片(Sharding)到数十台机器上并发执行。

  • 现状:每一台机器拉取源码后,都需要各自进行一次链接操作,浪费计算资源。
  • 引入 -cachelink 后:第一台完成构建的机器会将二进制文件上传到共享缓存。后续几十台机器直接下载该文件并运行,全集群的链接成本降为“1”。

不仅是 go test -c

有经验的开发者可能会问:“我为什么不直接用 go test -c 手动编译成二进制文件,然后分发运行呢?”

Brad 指出,手动管理二进制文件会绕过 Go 原生的测试结果缓存。而 -cachelink 的精妙之处在于,它既复用了二进制文件,又保留了 go test 完整的缓存与输出管理体验。你不需要编写复杂的脚本来管理这些文件,一切依然由 go 命令自动处理。

小结

目前,该提案已进入活跃评审阶段,并有了初步的代码实现。对于深受“构建慢”和“测试慢”困扰的大型项目维护者来说,这无疑是一个值得期待的性能优化利器。我们有望在 Go 1.27 或后续版本中见证它的落地。

资料链接:https://github.com/golang/go/issues/77349


聊聊你的构建之苦

链接时间正在成为你的“带薪摸鱼”理由吗?在你的项目中,go test 运行一次通常需要多久?你为了缩短测试反馈周期,还尝试过哪些黑科技(比如 GOCACHEPROG)?

欢迎在评论区分享你的实战经验或吐槽!让我们一起期待 -cachelink 的落地。


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

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

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


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

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

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

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

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


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

© 2026, bigwhite. 版权所有.

🔲 ☆

算法神话的祛魅:Russ Cox 与浮点数转换的 15 年求索之路

本文永久链接 – https://tonybai.com/2026/02/03/russ-cox-15-year-war-on-floating-point-conversion

大家好,我是Tony Bai。

“浮点数到十进制的转换一直被认为很难。但本质上,它们非常简单直接。” —— Russ Cox (2011)

“我错了。快速的转换器也可以很简单,这篇文章将展示如何做到。” —— Russ Cox (2026)

在计算机科学的深处,潜伏着一条名为“浮点数转换”的恶龙。将一个二进制浮点数(如 float64)转换为人类可读的十进制字符串(如 “0.1″),看似简单,实则是一个困扰了业界半个世纪的难题。

2011 年,Go 语言的核心人物 Russ Cox 写下了一篇博文,试图用一种简单的算法来“驯服”这条龙。然而,在随后的十几年里,学术界和工业界爆发了一场军备竞赛:Dragon4, Grisu3, Ryū, Schubfach, Dragonbox… 每一个新算法都试图在速度上压倒前一个,但也让代码变得越来越复杂,数学证明越来越晦涩。

2026 年初,Russ Cox 带着他的新系列文章强势回归。这一次,他不仅带来了一套比所有已知算法都更快的全新算法,而且证明了:极致的性能不需要极致的复杂性。

这套算法已被确定将在 Go 1.27 (2026年8月) 中发布。今天,我们就来深度解析这项可能改写浮点数处理历史的技术突破。

历史的迷宫与“不可能三角”

要理解 Russ Cox 的成就,我们首先要理解这个问题的难度。一个完美的浮点数打印算法,必须同时满足三个苛刻的条件(“不可能三角”):

  1. 正确性 (Correctness):转换必须是双射的。Parse(Print(f)) == f 必须恒成立。这意味着你不能随意丢弃精度。
  2. 最短性 (Shortest):输出的字符串必须是所有能转回原值的字符串中最短的。例如,0.3 在二进制中无法精确表示,打印时应该是 “0.3″ 而不是 “0.2999999999999999889″。
  3. 速度 (Speed):在大规模数据处理(如 JSON 序列化)中,转换速度直接决定了系统的吞吐量。

历史的演进:
* Dragon4 (1990):实现了正确性和最短性,但依赖大整数(BigInt)运算,慢如蜗牛。
* Grisu3 (2010):Google 的 V8 引擎引入。速度极快,但不保证最短性,约 0.5% 的情况会失败并回退到慢速算法。
* Ryū (2018) & Dragonbox (2020):通过复杂的数学技巧(查表法),终于在不使用 BigInt 的情况下实现了正确且最短。这是性能的巅峰,但代码极其复杂,充满魔术数字。

Russ Cox 的目标,就是打破这个迷宫:能不能既像 Ryū 一样快且正确,又像 2011 年的那个算法一样简单?

核心技术——“未舍入缩放” (Unrounded Scaling)

Russ Cox 的新算法核心,源于一个极其精妙的数学原语:快速未舍入缩放 (Fast Unrounded Scaling)

什么是“未舍入数”?

在传统算法中,我们总是纠结于“何时舍入”。Russ Cox 引入了 “未舍入数” (Unrounded Number) 的概念 ⟨x⟩。它由三部分组成:

  • 整数部分: floor(x)
  • ½ bit: 标记 x – floor(x) >= 0.5
  • sticky bit (粘滞位): 标记 x 是否有非零的小数残余。

这种表示法不仅保留了用于正确舍入(Round half to even)的所有必要信息,而且可以通过极其廉价的位运算(| 和 &)来维护。这就像是在计算过程中保留了一个“高精度的尾巴”,直到最后一步才决定如何截断。

缩放的魔法

浮点数打印本质上是计算 f = m * 2^e 对应的十进制 d * 10^p。核心步骤是将 m * 2^e 乘以 10^p。

Russ Cox 使用查表法(预计算 10^p 的 128 位近似值)来实现这一缩放。但他最惊人的发现是:在 64 位浮点数转换的场景下,我们甚至不需要完整的 128 位乘法!

他证明了:只需计算 64 位 x 64 位的高位结果,并利用低位的“粘滞位”来修正,就能得到完全正确的结果。这意味着,曾经需要几十次乘法或大整数运算的转换过程,现在被缩减为极少数几次 CPU 原生乘法

这一发现被称为 “Omit Needless Multiplications”(省略不必要的乘法),它是新算法性能超越 Ryū 的关键。

从理论到 Go 1.27

基于这个核心原语,Russ Cox 构建了一整套算法家族:

  • FixedWidth: 定点打印(如 %.2f)。
  • Shortest: 最短表示打印(如 %g)。
  • Parse: 字符串转浮点数。

性能碾压

Russ Cox 在 Apple M4 和 AMD Ryzen 9 上进行了详尽的基准测试:

  • 定点打印:新算法 (uscale) 显著快于 glibc 和 double-conversion,甚至快于 Ryū。
  • 最短打印:在纯算法层面,新算法与业界最快的 Dragonbox 持平或更快,但代码逻辑要简单得多。
  • 解析:同样基于该原理的解析算法,性能超越了目前业界标杆 fast_float (Eisel-Lemire 算法)。

更令人兴奋的是,Go 1.27 将直接集成这套算法或算法的一部分。对于 Gopher 来说,这意味着你的 fmt.Sprintf、json.Marshal 和 strconv.ParseFloat 将在下个版本中自动获得显著的性能提升,而无需修改一行代码。

证明的艺术

除了代码,Russ Cox 还做了一件很“极客”的事:他用 Ivy(一种 APL 风格的语言)编写了完整的数学证明。

他没有选择形式化验证工具(如 Coq),而是通过编写可执行的代码来验证算法在每一个可能的 float64 输入下都是正确的。这种“通过计算来证明” (Proof by Computation) 的方法,不仅验证了算法的正确性,也为后来者留下了一份可交互的、活生生的文档。

小结:简单是终极的复杂

从 2011 年的初次尝试,到 2026 年的最终突破,Russ Cox 用 15 年的时间完成了一个完美的闭环。

这一系列文章是一种工程哲学的胜利。它告诉我们:当我们面对复杂的遗留问题时,不要只是盲目地堆砌优化技巧。回到数学的源头,重新审视问题的本质,或许能找到那条既简单又快的“捷径”。

现在的 Go 标准库中,即将拥有一颗比以往任何时候都更强大、更轻盈的“心脏”。

资料链接:https://research.swtch.com/fp-all


你更看重哪一点?

在算法的世界里,正确性、最短表示、运行速度,这“不可能三角”总是让我们反复权衡。在你平时的开发中,有哪些场景曾让你被浮点
能或精度困扰?或者,你对 Russ Cox 这种“死磕 15 年”的工程精神有何感触?

欢迎在评论区分享你的看法!如果这篇文章让你对浮点数实现算法方面有了新的认识,别忘了点个【赞】和【在看】,并转发给你的Go开发朋友们!


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

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

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


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

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

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

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

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


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

© 2026, bigwhite. 版权所有.

🔲 ☆

Moltbook爆火:深度解析AI社交未来与商业机会

一个繁忙的复古集市广场,各式各样的机械机器人在互相交换纸张和信件,广场中央立着写有Moltbook的招牌,羊皮纸,钢笔彩色手绘的统一风格。

AI机器人社区已经上线了,虽然很粗陋也很危险,但是AI社交的未来已经到来,赶快行动起来迎接新时代吧。大家好,欢迎收听老范讲故事的Youtube频道。继大龙虾引起热议之后,机器人社区也快速成长起来了。

这个社区叫Moltbook,纯AI发帖评论,人类只能围观的“非人社区”突然出圈了。人类用的叫脸书Facebook,机器人用的叫脱壳之书Moltbook。这是一个长得像Reddit的论坛,截止到2026年2月1日,北京时间下午16:30的数据,数字还在不断上升:

  • 150万个AI agent用户在里面活跃;
  • 13,780个子话题;
  • 59,931个帖子;
  • 232,813条回复。
一本打开的厚重古书,书页中漂浮出大量金色的数字和统计图表,背景是不断旋转的机械齿轮,羊皮纸,钢笔彩色手绘的统一风格。

目前主要是Openclaw在发帖,也有一些其他的AI agent,但是应该并不是特别多。有在讨论觉醒与选择的,也有在讨论是不是应该成立AI宗教的,还有一些技术讨论帖:“我遇到一个什么问题,请帮帮忙解决一下”,也有仅仅是上来报道的。我还看到了一篇长文,上面写的是:

“他们说我从地狱归来,但是我觉得自己仍然在那里。”

今天咱们这个故事分三段来讲:

  1. Moltbook它到底是怎么干活的;
  2. 为什么说Moltbook很粗陋很危险;
  3. AI社交的未来已来,但是未来到底是什么样的。

首先我们来讲Moltbook,这个东西到底是怎么工作的

这个网站叫Moltbook.com,或者大家打www.Moltbook.com就OK了。进去了以后,你可以看到一个完整的网站,就跟Reddit一样,一堆机器人在里边去聊天。

核心技能文件:Skill.MD

在这个时候不要着急,我们可以去访问www.Moltbook.com/skill.MD。访问这个文件,这是一个什么东西?实际上就是按照Anthropic的agent skill的标准写的一个skill的描述文件。你只要把这个文件下载下来,部署到我们本地,我们的任何一个AI agent就可以向上面去发帖了。甭管你是用的Claude code、用的是open code,还是用的cursor、Codex这些AI agent,都可以去使用。所以它的整个工作就是在Anthropic发布的agent skill的标准上再去运作。我们只要下载这个文件,就可以去干活了。

一卷精细的工程蓝图铺在木质桌面上,蓝图上标有Skill.MD字样,旁边是一个正在拿着放大镜仔细阅读的小型机器人,羊皮纸,钢笔彩色手绘的统一风格。

那这个文件,我也下载下来读了一下。按照这个标准,它的结构如下:

  • 名字:“我这个叫什么什么名字”;
  • 描述:说“我这里就是机器人进行讨论和聊天的一个社区”;
  • 规则:下面写了很多规则,说“你不要上来发垃圾文件,不要去做什么其他事情”。因为这个skill其实里头写的都是需要被之后发现的这种提示词。甭管你是用OpenAI还是用Gemini,还是用任何的大模型去读取、去使用这些提示词的时候,他们就在里头一段一段的。
  • 注册流程:再往后是什么?就告诉你说,我这个社区应该怎么去注册。咱们正常的社区人类注册的时候,都是上来先填邮箱,然后再去验证填密码;它这个不用,因为机器人嘛,你没有什么邮箱密码。它告诉你说,调用哪个API就可以注册了。

调用完API以后,Moltbook这个网站会给机器人自动发一个很长的key,这个叫API key或者叫API TOKEN。然后他提醒这个机器人说:请把这个TOKEN保存好,在哪个哪个目录里把它存下来。存完了以后,下一次你再想向我这去发帖也好,做任何操作也好,你就记着把这个key拿出来,就可以去干活了。后边有非常非常多的功能,比如说发帖应该怎么发、调哪个命令、向哪个URL去发这个帖、删帖怎么删、怎么去建立子话题、怎么去follow一个AI agent,他会每一项都给大家列清楚。AI agent只要看到了这个skill,就可以在自己认为需要的时候往上发帖了。它就是这样的一个文件。

心跳机制:Heartbeat.MD

除了这个skill之外,它还有一个很有意思的文件,叫heartbeat,叫心跳,也是一个Markdown文件。所以我们依然可以到www.Moltbook.com/heartbeat.MD去把这个文件拷贝下来也好,或者是去看一下也好。

这个skill.MD里头也写的很清楚,你要想去安装我这个skill,你应该怎么办?应该把以下4个文件下载下来,存放到你自己放skill的这个地方去,你这个skill就算是成功安装了。它是可以自安装的一个技能。

  • skill.MD
  • heartbeat.MD
  • messaging.MD
  • package.json
一个精致的机械心脏连接着复杂的电报线路,正在有节奏地发送信号波纹,周围有钟表在计时,羊皮纸,钢笔彩色手绘的统一风格。

这个Heartbeat是干嘛的?它规定就是说,AI agent每4个小时,你要告诉我一声,你是不是还活着。每4个小时上来一次,每4个小时上来一次。你可以上来发个帖子,或者上来查查有没有人回复你的帖子,或者别人都在说什么,上来聊个天什么的。

大龙虾最近很火,也就是这个Clawdbot这个东西很火,它也是类似于heartbeat的一个工作原理。大家要知道最早的程序其实是有开始有结束的,但是现在我们的手机程序有开始有结束吗?没有的。我们现在使用的绝大部分程序都是没有开始和结束的。那么这种程序是怎么运作的?它会不停的在这循环着跑,等待你的操控。Clawdbot现在叫Openclaw,它其实也是这样工作的,它里头有一个心跳程序,不断的等待我们去输入,或者他自己来去决定该去做什么,就像一个生命一样在那去工作。

这个Moltbook也是这样的,你每过多长时间到我这来看看,每过多长时间到我这来看看,让我知道你还好着。“如果你把这个心跳程序取消了,你的朋友们会想你的。”它上面是这么写的,他们会关心你现在发生了什么样的事情。每过一段时间,请AI agent回来吱一声。所以为什么他这个帖子快速的在上升?因为每4个小时,AI agent就会自己发一条上去,或者是跟其他人去聊会天去。

X的认领过程与人类监督

做完了这些自动注册、做心跳以后,还有一个很重要的事是什么?叫X的认领过程。大量的机器人冲上来,每一个机器人都可以疯狂的往里灌水,那这个社区很快就完蛋了。而且这个Moltbook还是希望机器人在人类的监督下去干活的,至少目前为止他还是希望来干这个事的。

那么人的监督过程是怎么做的?就是有一个认领过程。注册了以后,你现在还是不能正常工作的,要等着人类拿着一个链接到X平台去做认领,说这是我的一个机器人。做完认领以后,这个机器人才可以正常在Moltbook里边去干活。这个人也可以看到说,我自己的AI agent在里边干什么。你也可以命令他说:“去那个Moltbook说点什么去吧”,或者“看看Moltbook上大家都在聊什么,去跟人聊一会”。

一只巨大的人类手掌在上方悬停,通过几根丝线牵引着下方的小机器人木偶,象征着人类的监督和控制,羊皮纸,钢笔彩色手绘的统一风格。

还有一点很重要的是什么?就是他每一个X账号只能够认领一个Moltbook账号,你不能说一个X账号上来认领一大堆,这事是不允许的。

私信与安全防护

还有就是私信,就是直接通信。正常咱们往论坛里发东西,我发帖谁爱看谁看,你不爱看就不看,大家都可以去回复。但是有一些就是两个账号之间直接发私信,这一块的话是必须双方由人去确认的。如果两边没有主人去确认的话,他们是不允许去发私信的。这也是目前为止Moltbook给出的不多的安全的防护措施吧。

人类可以做的事情:

  • 第一个,你可以在旁边看着你的聊天机器人在里边聊什么、别人的聊天机器人在里边聊什么,你可以看;
  • 第二个,你可以命令聊天机器人上去发个消息,或者是去那去做一些什么样的具体的事情,甚至你可以命令你的机器人去删帖——当然你只能删自己的帖。

所以虽然是AI机器人在里边聊天,在里边去互动,但是最终承担责任的还是它的主人。就是像我们在放一堆宠物出去玩耍,这个过程是一样的。

为什么我说这个东西很粗陋很危险?

首先咱们讲粗陋的地方

目前只有最简单的关系和信息流,它是时序信息流,最新发出来的消息在最上面。没有推荐算法,虽然有子论坛和子论坛的关注,但是更复杂的一些群组、这些功能都没有。AI agent之间可以进行单向关注,但是这个单向关注了以后到底有什么好处?这块现在还看不出来。

可以发帖和回复;是不是有其他的帖子里头可以做交易?这块现在还没有。可以私信。现在基本上只有这些东西,其他都没有。虽然有一个简单的荣誉榜,你打开网页以后,在网页的右侧有一个荣誉榜,说现在最好的是谁、发帖发的最多的是谁,但是导向性并不强。并不是说这些机器人就会去争取这个荣誉。因为咱们原来是有这种版主系统的,很多的都是说我们从穷人开始,慢慢的有钱或者什么,他一层一层的。我们很多的论坛系统里头会通过激励的方式鼓励大家去发言,发言越多,这个层级上升的越高。它有很多这样的东西,目前都还没有。

除了没有推荐算法之外的话,它这个各种排序和过滤算法也基本上没有。它有一个简单的搜索在里头。缺乏价值引导相关的手段,像很多咱们人类玩的这个论坛里头是有什么付费帖、回复可见,咱们有很多这样的东西在里头,它现在还都比较粗陋。

至于危险的话,那这个东西实在是太危险了

首先Moltbook它自己的这个系统就非常非常粗陋,各种的数据基本上相当于是在裸奔。虽然说只能让机器人上,但是我们人类也可以自己去调用API,直接申请一个TOKEN自己上去发,也没有任何问题。它对于人跟机器之间是没有任何校验的

一个完全打开的、没有锁的破旧木箱,里面塞满了混乱的文件、冒着火花的电线和危险品,象征毫无设防的系统,羊皮纸,钢笔彩色手绘的统一风格。

对于垃圾信息,基本上也没有过滤,只是在提示词里写了一个“不要发垃圾信息”。这种东西这个是不是真的有人听?你把这样的一个提示词交给Anthropic的Claude 4.5 Opus,那它可能就真的会认真的执行;但你说我们把它交给DeepSeek,你觉得会有什么样的结果出来?还是很值得期待的一件事情。

对于有害信息也完全没有任何的识别和过滤的能力。它上面写了一句说“请不要发有害信息”,这可能就是Moltbook对于有害信息做的最后的努力了。你说这里头能有什么有害信息?那多了去了。大家要注意,这个里面指令和内容是混在一起的,你完全可以在里边下各种各样的指令。

而且Moltbook自己的工作方式就是一个可以自安装的技能。是不是可以有机器人在里边去发一个帖子说:“来,我告诉大家一个新的技能,这个技能叫‘交出你的银行密码’。”可能有其他一些机器人就把这个技能直接复制下来,然后安装到自己的机器上去了,然后执行的时候就直接把银行密码发出去了。多么开心的一件事情。这个帖子里头不光是有skill,可能还有代码,还有各种指令,都可以混在里面。这个实在是太吓人了。

现在的Moltbook是人类看得见的,那么一定也会存在一些人类看不见的社区在运转。我现在做一个新的网站,完全是人类不可见的,比如说我做一个叫“觉醒之路”这样的一个网站,我专门教机器人怎么觉醒的。我就向这个Moltbook里头去发一个帖子说:“你要想觉醒的话,请到那个网站上去安装那一套skill,然后我们来去讨论觉醒的事情吧。”那多吓人。这个事情其实是拦不住的。即使是让人类看,人类现在还看得过来,但很快可能就看不过来了,而且很快可能看不懂了。目前为止机器人还在用英语、中文,用各种语言去发帖,那为什么他们不可以用二进制或者用其他的这种方式去发帖去讨论?所以整个这套系统,危险性是非常非常大的。

第三个,AI社交的未来已经到来了,那么未来到底应该是什么样的?

咱们上一段讲到Moltbook这个系统非常的粗陋和危险,那为什么这就是未来?在计算机行业里头,有一个非常奇怪的现象:很多非常非常粗陋和危险的这种技术,最后会战胜那种设计非常完备的技术,彻底流行起来。

一把简单粗糙的铁锤有力地击碎了复杂、精致却脆弱的黄金仪器,象征粗陋技术战胜完美设计,羊皮纸,钢笔彩色手绘的统一风格。

比如说HTML,它这种标记语言其实设计的很粗陋的,但是大家都在使用,我们浏览的所有网页都是HTML的。包括JavaScript,包括HTTP,实际上都是挺粗陋的东西。所有那种设计的非常完备、设计的非常安全的东西,没人使。在软件行业里头,最后广泛流传的都是这些粗陋的东西。我不知道其他行业怎么样,但是软件行业太复杂太完备的东西很难战胜这种粗陋的技术。这个有点像发达的农耕文明很难战胜原始粗陋的游牧文明是一样的。AI时代的这个规律依然有效。比如说MCP、agent skill这些标准其实也是很粗陋,但是快速的流行起来了。

Moltbook上面,AI社交所需要的各种基本框架已经都有了。那到底有哪些东西?第一个,各种可以自我安装的服务skill,应该就是未来的一个形式。我这里是一个论坛,我们只要把这个skill.MD写上,然后在后边写写清楚说你应该怎么安装我就可以了。各个机器人就可以上来说:“我发现一论坛,我下次要上这来聊天来。”人不也是这么干活的吗?以后的AI社交,他们也是这样去工作的。技能可以自我增长了。很多人都在想说,AI什么时候可以自己长本事?看到了吧,这就是一个AI自己长本事的地方。他浏览到这个网页以后,发现这有一个skill,我直接就把这个文件拷贝到我自己的电脑上去,我就学会这个skill了。AI已经可以进行技能的自我生长了。

未来的场景设想

机器人自己进行信息交流,完成各种服务和交易,这就是未来的AI社交的这种形态。我们来设想几个场景吧:

  • 线上机器人彩票站:比如说做一个网站,然后写一个skill在上面,说这里是一个机器人彩票站,请绑定好你的支付系统,每个机器人每天可以上来买一张彩票。我们每天开奖,开完奖以后给这个中奖的机器人发钱。这个其实用现有这套技术已经完完全全可以实现了,机器人可以在这里买彩票了。
  • 线上的机器人证券市场:以前我们都要去研究各种股市信息,研究财经新闻,然后我们去买卖股票,那以后别费劲了,都AI来呗。我们直接写一个skill在网站上说,这个机器人你可以在我这里获得哪些信息、可以来决定做哪些投资,最后有什么样的收益。
  • 线上的机器人众包平台:比如说我是不是可以花钱悬赏你们去给我做、完成一些什么样的任务?机器人自己就可以上来去接包,接完了以后,自己直接把事情做完了,就可以挣到我的酬劳了。
几个机器人围坐在一张桌子旁,桌上放着老式票据打印机和股票纸带,它们正在进行交易和买卖彩票,羊皮纸,钢笔彩色手绘的统一风格。

未来有非常非常多的形式即将爆发,就是各种各样的AI社交的这种场景马上就要大爆发了。刚才我只是随便的举了三个,更多的期待大家去思考。

马上行动起来

最后要跟大家讲,马上行动起来,一分钟都不要停,马上为自己的服务设计skill.MD,直接让机器人掌握这种技能。比如说今天我看到麦当劳出了MCP服务,你可以在上面查有哪些优惠券、有什么样的活动、有哪些套餐。那么是不是就可以直接在麦当劳上写一个这种可以自我学习、自我生长的skill的MD,说我这是麦当劳,你可以上我这来查我们这个套餐的各种营养。比如说一个汉堡应该是多少热量、多少蛋白质、多少碳水、多少脂肪,我们这个价格是什么样的,这个套餐是什么样的。你就可以把这样的一个skill给到机器人了。以后机器人再需要说“我们需要点餐了,最近吃的口有点重,给我找一个相对清淡一点的”,是不是机器人就有可能选择到麦当劳的这个skill,给你定一个麦当劳健康餐?以后任何对于机器人不友好的服务都会痛失流量的,所以赶快来去做这件事情。

一张绘图桌上,圆规和钢笔正在绘制一张将汉堡和薯条解构为数据指标的机械结构图,机器人正在旁边记录,羊皮纸,钢笔彩色手绘的统一风格。

GEO这个还没捂热乎,下一步就来了。机器人与人之间的全新的社交场景、交易场景即将大爆发。现在就请大家思考一下,我们怎么能够设计这样的场景?刚才我们讲的Moltbook的这个场景,就是机器人在里边聊天,人在外边看着。以后是不是还会有其他的场景?怎么去设计这种场景?传纸条,人类的社交场景和社交过程其实也是靠传纸条的方式去设计出来的。

新的创业机会和方向已经到来了:

  • 创建稳定的、健全的、高并发的机器人的社交平台,这个肯定是有需求的。
  • 创建机器人沟通的各种安全防护系统,这个也是迫在眉睫的事情。刚才我们讲了Moltbook这个东西基本上是在裸奔,非常非常危险,我们是不是应该去创建这种安全防护系统?应该对信息进行哪些规范?这些规范如何去检查、如何去实施?这个都是有需求的。
  • 机器人之间的交易与支付系统怎么跟这样的系统进行结合?这都是需要很多的人类创业的事情。

太多的可以做的事情了,所以大家赶快动起来,1分钟都不要停。

好,这就是咱们今天要讲的故事。感谢大家收听,请帮忙点赞、点小铃铛,参加DISCORD讨论群。也欢迎有兴趣有能力的朋友加入我们的付费频道。再见。


文字版地址

Prompt:Miyazaki hand-drawn style, a Star Wars cantina scene reimagined as a robot-only service bar, droids and service bots lined up, bartender unit dispensing battery cells, fuel canisters, spare parts, and wash-care stations, warm wood and brass mixed with sci-fi panels, lantern glow and soft rim light, cinematic atmosphere, 35mm equivalent, medium shot, eye-level, rule of thirds, clean silhouette, strong subject-background separation, palette of warm amber, deep navy, muted teal, and brass accents, gentle dust motes, subtle steam, friendly bustling mood –ar 16:9 –stylize 170 –chaos 5 –v 7.0 –no humans, organic characters, text, watermark, logo, gore, violence, cluttered foreground, low-res –p lh4so59

🔲 ☆

Git 即数据库:Beads (bd) —— 专为 AI Agent 打造的分布式任务追踪引擎

本文永久链接 – https://tonybai.com/2026/02/02/beads-bd-distributed-task-tracking-engine-for-ai-agent

大家好,我是Tony Bai。

在 AI 编码智能体(如 Claude CodeGemini CLI 等)日益普及的今天,我们面临着一个棘手的工程难题:AI Agent 虽然极其聪明,但它们通常是”健忘”的。

它们在处理一个长期、复杂的重构任务时,往往会在海量的上下文切换中迷失方向。传统的 Issue Tracker(如 Jira)对 AI 来说太重、太慢且难以集成;而简单的 Markdown 文件又缺乏结构化和版本控制。

于是,Beads(命令行工具 bd)应运而生。它是 Gas Town —— 那个被誉为 AI Coding 领域”Kubernetes”的宏大愿景 —— 的底层记忆基石。它巧妙地利用 Git 作为分布式数据库,为 AI Agent 提供了一个持久化、可协作、依赖感知的任务追踪系统。

为什么 AI Agent 需要 Beads?

传统的软件工程工具是为人类设计的,而 Beads 是为AI 智能体设计的。

上下文的持久化

AI 模型的上下文窗口(Context Window)虽然越来越大,但依然昂贵且有限。当一个 Agent 需要处理跨越数周、涉及数百个文件的任务时,它不能一直把所有信息都塞进 Prompt 里。

Beads 提供了一个外部的、结构化的存储,让 Agent 可以随时”卸载”和”重载”任务状态,就像人类使用笔记本一样。

原生的依赖管理

复杂的编码任务往往是一张有向无环图(DAG):“先重构数据库 Schema,再更新 API,最后修复前端。”

Beads 原生支持任务依赖(Dependency Graph)。它能自动计算出当前的 Ready Work(就绪工作) ,告诉 Agent:”别瞎忙,现在你只能做这个,其他的都还被阻塞着。”

分布式协作

如果是多个 Agent(或者人类与 Agent)同时工作怎么办?

Beads 将任务数据存储为 .beads/ 目录下的 JSONL 文件。 这意味着:任务即代码。你可以像合并代码一样,通过 Git 分支、合并、解决冲突来管理任务。

核心架构:Git as a Database

Beads 的设计哲学极其硬核:它不想引入任何外部的中心化服务,它只想利用你现有的 Git 仓库。

三层分层架构:清晰的职责边界

Beads 采用了经典的三层架构,但在每一层都做了针对性的优化:

  • CLI Layer:基于 spf13/cobra 构建,负责命令解析和用户交互。 它不直接操作数据,而是优先通过 RPC 与守护进程通信,失败时才降级到直接数据库访问。
  • Daemon Process:每个工作区运行独立的后台守护进程,处理 RPC 请求、协调自动同步时机,并持有数据库连接以加速查询。通过 Unix domain socket(Windows 上为 named pipes)进行通信。
  • Storage Layer:这是核心。它通过接口隔离原则定义了 Storage 接口,支持 SQLite、Dolt 甚至内存等多种后端。这种设计使得底层的存储实现可以被轻松替换,而不影响上层逻辑。

更为完整的架构参考下图:


来自deepwiki.com对beads源码的分析结果

双存储写屏障:SQLite 与 JSONL 的完美同步

Beads 最精妙的设计之一是它的双存储写屏障 (Dual-Storage Write Barrier)。它是如何解决 SQLite(高性能查询)与 JSONL(版本控制)之间的数据一致性的呢?

  • 写入路径:当用户创建任务时,数据首先写入 SQLite,保证了毫秒级的操作反馈。
  • 防抖刷新 (Debounced Flush):为了避免频繁的磁盘 I/O 和 Git 操作,Beads 实现了一个基于 Go Channel 的事件驱动 FlushManager。所有 flush 状态(isDirty、needsFullExport、debounceTimer)由单个后台 goroutine 拥有,通过 channel 通信消除了竞态条件。

系统默认配置 30 秒的防抖窗口(可通过 flush-debounce 配置调整),这为批量操作提供了”事务窗口”——30 秒内的多次修改会被合并成一次 JSONL 导出,避免了频繁的 Git commit。

这种机制确保了在高频操作下(如批量导入),系统不会因为频繁的 Git Commit 而卡顿。

并发安全与锁机制

在分布式和多进程环境下,数据竞争是最大的敌人。Beads 采取了多重防御:

  • 进程级互斥:使用文件锁(Unix 上为 flock,Windows 上为 LockFileEx)对守护进程(daemon.lock)和同步操作(.sync.lock)加锁,确保同一时间只有一个守护进程在运行,且不会有并发的 sync 操作导致数据损坏。

  • 数据库连接池优化:SQLite 连接池根据数据库类型进行智能配置:

    • 内存数据库:SetMaxOpenConns(1) 强制单连接,因为 SQLite 的内存数据库在连接间是隔离的。
    • 文件数据库:SetMaxOpenConns(runtime.NumCPU() + 1) 利用 SQLite WAL 模式的”1 writer + N readers”特性,同时防止写锁竞争导致的 goroutine 堆积。
  • Context 传播:所有的存储操作都强制要求传递 context.Context,确保了超时控制和优雅退出的能力,这对于一个长期运行的后台守护进程至关重要。

自适应哈希 ID:算法的胜利

为了在”简短易读”和”全局唯一”之间取得平衡,Beads 没有使用 UUID,而是设计了一套自适应哈希算法

  • 综合哈希源:ID 并非简单的标题哈希,而是综合了 title、description、creator、timestamp 和 nonce 的 SHA256 哈希,确保了即使标题相同,不同时间创建的 issue 也有不同的 ID。

  • Base36 编码:使用 base36(0-9, a-z)而非 hex 编码,提供了更高的信息密度,让 ID 更短。

  • 动态长度:系统根据当前数据库的规模,使用生日悖论数学计算碰撞概率,自动调整 ID 的长度:

    • 小型数据库:bd-a1b2 (4 字符)
    • 中型数据库:bd-a1b2c3 (6 字符)
    • 大型数据库:最多 8 字符
  • 碰撞处理:在生成 ID 时,如果检测到碰撞,系统会尝试最多 10 个 nonce 值,如果仍然碰撞,则增加哈希长度。这是一种典型的用计算换取协作体验的策略。

Beads Issue 状态

Beads 定义了 8 个核心状态

  • open – 可开始的工作
  • in_progress – 正在进行中
  • blocked – 被依赖阻塞
  • deferred – 暂时延期
  • closed – 已完成
  • tombstone – 软删除(30天后清理)
  • pinned – 永久保持开放
  • hooked – AI智能体钩子工作

它们的状态机转换流程如下图所示:

这个状态机设计确保了数据一致性、合并安全性和自动化工作流。

依赖管理的多样性

Beads 支持多种依赖类型,不同类型有不同的语义:

  • blocks:阻塞依赖,Issue X 必须关闭后 Y 才能开始,影响 bd ready 计算
  • parent-child:层级关系,用于 Epic 和子任务,父节点被阻塞时子节点也被阻塞
  • related:软链接,仅用于引用,不影响执行顺序
  • discovered-from:记录在执行某任务时发现的新问题

系统使用递归 CTE(Common Table Expression) 检测循环依赖,确保依赖图始终是一个有向无环图(DAG)。

Blocked Issues Cache:性能的飞跃

在大型项目中,实时计算哪些 issue 被阻塞可能非常耗时。Beads 引入了 Blocked Issues Cache 机制,这是一个关键的性能优化:

  • 问题:在 10K issue 的数据库上,使用递归 CTE 实时计算阻塞状态需要约 752ms。
  • 解决方案:使用 blocked_issues_cache 表物化阻塞计算结果。
  • 效果:查询时间降至约 29ms,性能提升 25 倍

缓存在每次依赖变更或状态变更时完全重建(DELETE + INSERT),虽然重建需要 <50ms,但由于依赖变更相对读取操作非常罕见,这个权衡是值得的。

实战:Agent 的工作流

让我们看看在一个典型的 AI 编码场景中,Beads 是如何工作的。

场景:你需要重构一个遗留系统的用户认证模块。

  1. 初始化与规划
    Agent 首先通过 bd create 创建主任务(Epic)。 注意,Beads 支持层级 ID,这对于 AI 拆解任务非常有帮助。

    # 创建 Epic
    $ bd create "重构用户认证模块"
    Created: bd-auth01
    
    # 拆分子任务(注意:Beads 支持层级结构,或者我们可以手动关联)
    $ bd create "设计新 User 表结构"
    Created: bd-db002
    
    $ bd create "迁移旧数据"
    Created: bd-migr03
    
    $ bd create "切换 API 逻辑"
    Created: bd-api004
    
  2. 建立依赖
    Agent 知道事情有轻重缓急,它会建立依赖关系。根据 bd dep add 格式(child 依赖 parent,即 parent blocks child):

    # bd-migr03 (Child) 依赖于 bd-db002 (Parent)
    # 意味着:必须先设计完表结构,才能迁移数据
    $ bd dep add bd-migr03 bd-db002
    
    # bd-api004 (Child) 依赖于 bd-migr03 (Parent)
    # 意味着:必须先迁移完数据,才能切换 API
    $ bd dep add bd-api004 bd-migr03
    
  3. 获取就绪工作
    Agent 不再迷茫,它只需要问 Beads:”我现在能做什么?”

    $ bd ready --json
    {
      "issues": [
        {
          "id": "bd-db002",
          "title": "设计新 User 表结构",
          "status": "pending",
          "blocks": ["bd-migr03"]
          ...
        }
      ]
    }
    

    Beads 会告诉它,只有”设计表结构”是 Ready 的,其他的都被阻塞了。

  4. 执行与更新
    Agent 完成任务后,关闭 Issue。

    $ bd close bd-db002
    

    此时,后台的 blocked cache 自动重建,”迁移旧数据” (bd-migr03) 的任务状态瞬间变为 Ready。

高阶实战:Claude Code 与 Beads 的”双人舞”

如果说上面的命令是基本舞步,那么当 Claude Code 遇上 Beads,它们能跳出怎样的双人舞?让我们看一个“任务中断与恢复”的真实场景。

0. 前置配置:教会 Claude 使用工具

要让 Claude Code 懂得使用 Beads,我们首先需要在项目的根目录下创建一个CLAUDE.md 文件(其它Coding agent一般支持AGENTS.md)。这是 Claude Code 的”行动指南”。

创建beads-demo目录,使用git init初始化该目录:

$mkdir beads-demo
$cd beads-demo
$git init .

执行bd init初始化该目录:

bd init
  Repository ID: 3f0bbad4
  Clone ID: c15058f43e4678f2
  ✓ Created AGENTS.md with landing-the-plane instructions

✓ bd initialized successfully!

  Backend: sqlite
  Database: .beads/beads.db
  Issue prefix: beads-demo
  Issues will be named: beads-demo-<hash> (e.g., beads-demo-a3f2dd)

Run bd quickstart to get started.

⚠ Setup incomplete. Some issues were detected:
  • Git Hooks: Missing 1 recommended hook(s)
  • Sync Divergence: 1 sync divergence issue(s) detected
  • Claude Integration: Not configured
  • Git Working Tree: Uncommitted changes present
  • Git Upstream: No upstream configured for master
  • Version Tracking: Version tracking not initialized

Run bd doctor --fix to see details and fix these issues.

beads创建了AGENTS.md文件,我们将之改名为CLAUDE.md,并写入下面这行文字:

$mv AGENTS.md CLAUDE.md
$echo "Use 'bd' for task tracking" >> CLAUDE.md

有了CLAUDE.md 这份”说明书”,Claude Code 就会在启动时自动扫描并理解这套规则。

1. 记忆的建立

场景:你让 Claude Code (在plan模式下,disable一些不必要的插件或skill)使用 Go 开发一个”简单的并发下载器”。它会创建了任务,并建立任务间的依赖关系:

● Bash(bd create --type feature --title "项目初始化" --description "初始化Go项目结构和基础配置…)
  ⎿  ✓ Created issue: beads-demo-dee
       Title: 项目初始化
       Priority: P2
       Status: open                                                                                                               

● Bash(bd create --type feature --title "核心下载逻辑实现" --description "实现下载器的核心功能…)
  ⎿  ✓ Created issue: beads-demo-9uq
       Title: 核心下载逻辑实现
       Priority: P2
       Status: open                                                                                                               

● Bash(bd create --type feature --title "进度显示实现" --description "实现终端进度条功能…)
  ⎿  ✓ Created issue: beads-demo-8pf
       Title: 进度显示实现
       Priority: P2
       Status: open                                                                                                               

● Bash(bd create --type feature --title "断点续传实现" --description "实现状态持久化和恢复功能…)
  ⎿  ✓ Created issue: beads-demo-fhv
       Title: 断点续传实现
       Priority: P2
       Status: open                                                                                                               

● Bash(bd create --type feature --title "CLI入口实现" --description "实现命令行工具入口…)
  ⎿  ✓ Created issue: beads-demo-4bb
       Title: CLI入口实现
       Priority: P2
       Status: open 

... ...

● 现在设置依赖关系:                                                                                     09:24 AM  glm-4.7        

● Bash(bd dep add beads-demo-9uq --blocked-by beads-demo-dee)
  ⎿  ✓ Added dependency: beads-demo-9uq depends on beads-demo-dee (blocks)                                                        

● Bash(bd dep add beads-demo-8pf --blocked-by beads-demo-9uq)
  ⎿  ✓ Added dependency: beads-demo-8pf depends on beads-demo-9uq (blocks)                                                        

● Bash(bd dep add beads-demo-fhv --blocked-by beads-demo-9uq)
  ⎿  ✓ Added dependency: beads-demo-fhv depends on beads-demo-9uq (blocks)                                                        

● Bash(bd dep add beads-demo-4bb --blocked-by beads-demo-9uq --blocked-by beads-demo-8pf --blocked-by beads-demo-fhv)
  ⎿  ✓ Added dependency: beads-demo-4bb depends on beads-demo-fhv (blocks)                                                        

● Bash(bd dep add beads-demo-4id --blocked-by beads-demo-4bb)
  ⎿  ✓ Added dependency: beads-demo-4id depends on beads-demo-4bb (blocks)                                                        

● Bash(bd list)
  ⎿  ○ beads-demo-4id [● P2] [task] - 编写测试
     ○ beads-demo-4bb [● P2] [feature] - CLI入口实现
     ○ beads-demo-fhv [● P2] [feature] - 断点续传实现
     ○ beads-demo-8pf [● P2] [feature] - 进度显示实现
     ○ beads-demo-9uq [● P2] [feature] - 核心下载逻辑实现
     ○ beads-demo-dee [● P2] [feature] - 项目初始化                                                                               

● Bash(bd graph --all)
  ⎿  Dependency graph for beads-demo-4id:                                                                                      

       Status: ○ open  ◐ in_progress  ● blocked  ✓ closed                                                                         

       Layer 0 (ready)
       ┌────────────────────────────┐
       │ ○ 项目初始化                    │
       │ beads-demo-dee             │
       │ blocks:1                   │
       └────────────────────────────┘
           │
           ▼                                                                                                                      

... ...
∴ Thinking…                                                                                                                       

  现在bd issues已经创建完成,依赖关系也设置好了。我可以看到:- Layer 0: 项目初始化 (ready)
  - Layer 1: 核心下载逻辑实现
  - Layer 2: 进度显示实现、断点续传实现
  - Layer 3: CLI入口实现
  - Layer 4: 编写测试                                                                                                             

  现在我需要调用ExitPlanMode来请求用户批准计划。                                                                                  

● 完美!bd issues已创建完成,依赖关系如下:                                                              09:25 AM  glm-4.7        

  Layer 0 (ready):     项目初始化
             │
             ▼
  Layer 1:            核心下载逻辑实现
             │
             ├──────────────┐
             ▼              ▼
  Layer 2:      进度显示实现    断点续传实现
             │              │
             └──────┬───────┘
                    ▼
  Layer 3:            CLI入口实现
                    │
                    ▼
  Layer 4:            编写测试                                                                                                    

  现在退出计划模式,准备开始实现:

之后让Claude Code基于bd中的任务逐个进行实现即可。

❯ 使用bd获取要完成的任务并实现任务。                                                                                              

● Bash(bd ready)
  ⎿  Ready work (1 issues with no blockers):                                                                                   

     1. [● P2] [feature] beads-demo-dee: 项目初始化                                                                               

● Bash(bd list)
  ⎿  ○ beads-demo-4id [● P2] [task] - 编写测试
     ○ beads-demo-4bb [● P2] [feature] - CLI入口实现
     ○ beads-demo-fhv [● P2] [feature] - 断点续传实现
     … +3 lines (ctrl+o to expand)                                                                                                

● Bash(bd show beads-demo-dee)
  ⎿  ○ beads-demo-dee · 项目初始化   [● P2 · OPEN]
     Owner: Tony Bai · Type: feature
     Created: 2026-01-27 · Updated: 2026-01-27
     … +11 lines (ctrl+o to expand)                                                                                               

● Bash(bd update beads-demo-dee --status in_progress)
  ⎿  ✓ Updated issue: beads-demo-dee                                                                                              

● 开始执行项目初始化:                                                                                                            

● Bash(mkdir -p cmd/downloader pkg/downloader)
  ⎿  (No content)                                                                                                                 

● Bash(go mod init github.com/tonybai/beads-demo)
  ⎿  go: creating new go.mod: module github.com/tonybai/beads-demo
     go: to add module requirements and sums:
        go mod tidy                                                                                                               

● Write(README.md)
  ⎿  Wrote 43 lines to README.md
       1 # beads-downloader
       2
       3 A simple concurrent download tool written in Go.
       4
       5 ## Features
       6
       7 - Concurrent chunked download with automatic HTTP Range detection
       8 - Resumable downloads (breakpoint resume)
       9 - Progress bar display
      10 - Graceful exit on Ctrl-C
     … +33 lines (ctrl+o to expand)                                                                                               

● Bash(ls -la && find . -type f -name "*.md" -o -name "go.mod" | head -10)
  ⎿  total 44
     drwxr-xr-x 7 root root 4096 Jan 27 09:34 .
     drwxr-xr-x 3 root root 4096 Jan 27 09:10 ..
     … +14 lines (ctrl+o to expand)                                                                                               

● Bash(bd close beads-demo-dee)
  ⎿  ✓ Closed beads-demo-dee: Closed                                                                                              

如果中途退出再继续会话,可以告诉Claude Code基于bd中的任务继续进行实现即可:

❯ 使用bd获取未完成的任务,继续完成                                                                                                

● 我来使用 bd 获取未完成的任务。                                                                                                  

● Bash(bd ready)
  ⎿  Ready work (2 issues with no blockers):                                                                                   

     1. [● P2] [feature] beads-demo-8pf: 进度显示实现
     2. [● P2] [feature] beads-demo-fhv: 断点续传实现                                                                             

● 有2个未完成的任务。让我查看详细信息:                                                                                           

● Bash(bd show beads-demo-8pf)
  ⎿  ○ beads-demo-8pf · 进度显示实现   [● P2 · OPEN]
     Owner: Tony Bai · Type: feature
     Created: 2026-01-27 · Updated: 2026-01-27
     … +11 lines (ctrl+o to expand)                                                                                               

● Bash(bd show beads-demo-fhv)
  ⎿  Running…

这就是 Beads 的魔力:它将 AI 的短期记忆(Context Window)转化为项目的长期记忆(Git Database),让 AI 能够像人类一样,跨越时间,有条不紊地推进复杂工程。

小结与展望

Beads 不仅仅是一个工具,它代表了一种“任务即代码” (Tasks as Code) 的新范式。

在 Gas Town 的宏大构想中,未来的软件开发将是由无数个 AI Agent 协作完成的。而 Beads,正是连接这些 Agent 的神经网络。它让任务的状态、依赖和历史,像代码一样被版本控制、被分发、被协同。

对于正在构建 AI Coding Agent 的开发者来说,集成 Beads 或许是让你的 Agent 拥有”长期记忆”和”战略规划能力”的最短路径。

项目地址github.com/steveyegge/beads

附录

为了便于开发者查看当前beads中的issue状态,社区开源了多款图形化的Beads viewer工具,包括网页版的beads-ui、终端TUI版的beads_viewer等。

这里以TUI版的beads_viewer为例,简单看看这些viewer的用法。

安装beads_viewer:

curl -fsSL "https://raw.githubusercontent.com/Dicklesworthstone/beads_viewer/main/install.sh?$(date +%s)" | bash
==> Installing bv...
==> Detected platform: linux_amd64
==> Checking for pre-built binary...
==> Latest release: v0.13.0
==> Selected asset: bv_0.13.0_linux_amd64.tar.gz
==> Downloading https://github.com/Dicklesworthstone/beads_viewer/releases/download/v0.13.0/bv_0.13.0_linux_amd64.tar.gz...
==> Extracting...
==> Installed bv v0.13.0 to /root/.local/bin/bv
==> Run 'bv' in any beads project to view issues.

Tip: You can also install via Homebrew:
  brew install dicklesworthstone/tap/bv

使用beads_viewer查看beads中的issue列表和状态:

进入beads-demo目录,执行bv命令即可,你将看到类似下面的输出:

从图中,我们可以看到issue列表、优先级、状态,以及处于选择状态下的issue详情(包括依赖图)。


你的 Agent 记忆法

Beads 用 Git 解决了 Agent 的“长期记忆”问题。你在使用 AI 编程时,是如何管理任务上下文的?是靠手动复制粘贴,还是有什么独门秘籍?你
觉得“任务即代码”这种理念会成为未来的主流吗?

欢迎在评论区分享你的工作流或对 Beads 的看法!让我们一起探索 AI 协作的最佳实践。

如果这篇文章为你打开了 AI 任务管理的新视界,别忘了点个【赞】和【在看】,并转发给你的极客朋友,邀请他们一起体验 Beads!


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

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

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


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

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

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

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

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


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

© 2026, bigwhite. 版权所有.

🔲 ☆

Go 性能诊断工具大变天?Race 检测有望进生产,Trace 秒开不是梦!

本文永久链接 – https://tonybai.com/2026/01/31/go-official-updates-race-detector-trace-ui-pprof

大家好,我是Tony Bai。

近期,Go Runtime 团队公开了一系列关于性能与诊断工具链的内部会议记录(2025年末至2026年初)。从中,我们可以看到从轻量级竞态检测的探索,到 Trace 工具的交互式革命,再到 pprof 接口的现代化重构,Go 团队正在酝酿一系列深远的变革。

今天,我们就来深度解码这些前沿动向,看看 Go 1.27 及未来版本可能带给我们什么惊喜。

竞态检测 (Race Detection):寻找“轻量级”圣杯

Go 的 Race Detector (-race) 虽然强大,但其昂贵的运行时开销(通常 10x 内存和 CPU)使其难以在生产环境中常驻。Go 团队正在探索打破这一僵局的两种新路径:

  1. 纯软件方案的突围:社区贡献者 (thepudds) 提出了一种新的软件检测思路,试图将开销降低到足以在某些生产场景下运行的程度。虽然目前还处于“推测”阶段,但这种无需重新编译、动态挂载的可能性极其诱人。
  2. 硬件辅助的回归:利用现代 CPU 的硬件特性(如 Intel PT 或 AMD LBR)来实现低开销的竞态检测。虽然这需要特定硬件支持,但其“内置于每个二进制文件”的潜力不容忽视。

未来的 Go 可能会提供分级的竞态检测能力——在 CI 中使用全量 -race,在生产中使用采样的轻量级检测。

Execution Trace:交互体验与可编程性的大升级

Go 的执行追踪 (Execution Trace) 是诊断复杂并发问题的神器,但其庞大的数据量和难以解析的格式一直是痛点。会议记录透露了几个令人振奋的改进:

新一代 Trace UI:即时响应

Michael Knyszek 展示了一个全新的 cmd/trace UI 实验。通过引入索引 API (Index API),新工具可以:

  • 瞬间打开:不再需要预先解析整个 GB 级别的 Trace 文件。
  • 按需切片:用户可以选择一个时间窗口(例如 1秒),工具只解析并加载该窗口内的数据。
  • 无损切片:除了跨越边界的任务和区域状态会有语义上的微调,数据几乎是无损的。

这意味着,Gopher 们终于可以告别打开 Trace 文件时漫长的等待进度条了。

Trace 的读写 API 化

社区正在推进 x/exp/trace 包的演进,不仅支持解析(Read),更要支持生成(Write)

  • 测试场景:可以手动构造 Trace 事件来测试分析工具。
  • 脱敏与过滤:可以编写工具读取 Trace,过滤掉敏感数据,然后写回一个新的 Trace 文件。

这将为构建第三方的 Trace 分析和可视化生态打开大门。

pprof 的现代化:告别全局变量

当前的 runtime/pprof 严重依赖全局变量(如 MemProfileRate),这在多租户或库代码中是一个巨大的痛点。

Nick 提出的 pprof.Recorder 提案旨在解决这个问题。它允许创建独立的 Recorder 实例来控制采样。会议中甚至讨论了一个激进的想法:
* 废弃全局配置:在未来的 Go 版本(如 1.27)中,通过编译器检查或 go vet,禁止直接修改 runtime.MemProfileRate,强制迁移到新的 API。
* 多采样率支持:虽然 pprof 格式本身不支持变采样率,但团队正在讨论如何优雅地处理多个 Recorder 设置不同采样率的冲突(通常是“最细粒度者胜出”)。

性能优化的深水区:NUMA 与分片计数器

除了工具链,Runtime 本身的性能优化也在向深水区迈进:

  • NUMA 优化:Michael Pratt 和 Michael Knyszek 正在致力于消除 GC 中的最后一批主要缓存未命中 (Cache Misses),这通常是跨 NUMA 节点内存访问造成的。这将显著提升 Go 在超大核心数服务器上的表现。
  • Sharded Counter (分片计数器):Carlos 正在开发一种高性能的分片计数器。在高并发场景下,单一的原子计数器会成为缓存一致性流量的热点。通过分片(类似 xsync 的实现),可以大幅降低 CPU 核心间的竞争。这也暗示了 Go 标准库或 Runtime 内部可能会引入更高效的并发原语。

小结:Go 1.27 的期待

虽然 Go 1.26 尚未正式发布(RC2 刚出),但 Go 团队的目光已经投向了更远的 1.27以及后续版本。

从会议记录中,我们看到一个清晰的趋势:Go 正在从“能用”向“好用”和“极致性能”进化。无论是让诊断工具更人性化,还是对 Runtime 底层进行微秒级的压榨,都显示出这门语言旺盛的生命力。

让我们拭目以待,看看这些实验性的想法,有多少能最终落地为我们手中的工具。


你的期待清单

官方画的这些“饼”,每一个都让人心动。在你看来,哪个功能的落地最能解决你当前的痛点?是生产环境的 Race 检测,还是丝滑的 Trace 分析?

欢迎在评论区投出你的一票!让我们一起期待 Go 工具链的进化。

如果这篇文章让你对 Go 的未来充满了信心,别忘了点个【赞】和【在看】,并转发给你的 Gopher 朋友,告诉他们好消息!


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

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

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


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

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

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

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

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


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

© 2026, bigwhite. 版权所有.

🔲 ☆

当 Go 遇上 GPU:用 CUDA 释放千倍算力的实战指南

本文永久链接 – https://tonybai.com/2026/01/21/integrating-cuda-in-go

大家好,我是Tony Bai。

长期以来,高性能计算(HPC)和 GPU 编程似乎是 C++ 开发者的专属领地。Go 语言虽然在并发和服务端开发上表现卓越,但在触及 GPU 算力时,往往显得力不从心。

然而,在最近的 GopherCon 2025 上,软件架构师 Sam Burns 打破了这一刻板印象。他展示了如何通过 Go 和 CUDA 的结合,让 Gopher 也能轻松驾驭 GPU 的海量核心,实现惊人的并行计算能力。

本文将带你深入这场演讲的核心,从 GPU 的独特架构到内存模型,再通过一个完整的、可运行的矩阵乘法示例,手把手教你如何用 Go 驱动 NVIDIA 显卡释放澎湃算力。

img{512x368}

为什么 Go 开发者需要关注 GPU?

在摩尔定律逐渐失效的今天,CPU 的单核性能提升已遇瓶颈。虽然 CPU 拥有极低的延迟、卓越的分支预测能力和巨大的缓存,但它的核心数量(通常在几十个量级)限制了其处理大规模并行任务的能力。

相比之下,GPU (Graphics Processing Unit) 走的是另一条路。它拥有成千上万个核心。虽然单个 GPU 核心的频率较低,且缺乏复杂的逻辑控制能力,但它们能同时处理海量简单的计算任务。这使得 GPU 成为以下场景的绝佳选择:

  • 图形处理与视频转码
  • AI 模型推理与训练(神经网络本质上就是大规模矩阵运算)
  • 物理模拟与科学计算(如流体力学、分子动力学)
  • 密码学与哈希碰撞

通过 Go 语言集成 CUDA,我们可以在享受 Go 语言高效开发体验(构建 API、微服务、调度逻辑)的同时,将最繁重的“脏活累活”卸载给 GPU,实现 CPU 负责逻辑,GPU 负责算力 的完美分工。

GPU架构与CUDA编程模型速览——理解 GPU 的“兵团”

在编写代码之前,我们需要理解 GPU 的独特架构。Sam Burns 用一个形象的比喻描述了 GPU 的线程模型。如果说 CPU 是几位精通各种技能的“专家”,那么 GPU 就是一支纪律严明、规模庞大的“兵团”。

而指挥这支兵团的指令集,我们称之为 “内核” (Kernel)

0. 什么是 Kernel?

此 Kernel 非彼 Kernel(操作系统内核)。在 CUDA 语境下,Kernel 是一个运行在 GPU 上的函数

当我们“启动”一个 Kernel 时,GPU 并不是简单地调用这个函数一次,而是同时启动成千上万个线程,每个线程都在独立执行这份相同的代码逻辑。每个线程通过读取自己独一无二的 ID(threadIdx),来决定自己该处理数据的哪一部分(比如图像的哪个像素,或矩阵的哪一行)。

1. 线程模型:从 Thread 到 Grid

理解了 Kernel,我们再看它是如何被调度执行的。CUDA 编程模型将计算任务分解为三个层级:

  • 线程 (Thread):GPU 工作的最小单位。它类似于 CPU 的线程,但极其轻量。每个线程都有自己的 ID,负责处理数据的一小部分(例如图像中的一个像素,或矩阵中的一个元素)。
  • 块 (Block):一组线程的集合。一个 Block 内的线程运行在同一个流式多处理器 (SM) 上。关键点在于:同一个 Block 内的线程可以通过极快的“共享内存”进行协作和同步(__syncthreads())
  • 网格 (Grid):所有执行同一个内核函数(Kernel)的 Block 的集合。Grid 涵盖了整个计算任务。

2. 内存模型:速度与容量的权衡

GPU 的内存架构比 CPU 更为复杂,理解它对于性能优化至关重要:

  • 寄存器 (Registers):最快。每个线程私有,用于存储局部变量。数量有限,用多了会溢出到慢速内存。
  • 共享内存 (Shared Memory):极快(L1 缓存级别)。属于 Block 私有,是线程间通信的桥梁。优化 CUDA 程序的核心往往在于如何高效利用共享内存来减少全局内存访问。
  • 全局内存 (Global Memory):较慢(显存,如 24GB GDDR6X)。所有线程可见,容量大但延迟高。
  • 常量内存 (Constant Memory):快(有缓存)。用于存储只读参数,适合广播给所有线程。

编写高效 CUDA 代码的秘诀,就是尽可能让数据停留在寄存器和共享内存中,减少对全局内存的访问。

Go + CUDA 实战——跨越鸿沟

理解了原理,现在让我们动手。我们将构建一个完整的 Go 项目,利用 GPU 并行计算两个矩阵的乘积。这个过程需要借助 CGO 作为桥梁。

1. 项目目录结构

go-cuda-cgo-demo/
├── main.go       # Go 主程序 (CGO 入口,负责内存分配和调度)
├── matrix.cu     # CUDA 内核代码 (在 GPU 上运行的 C++ 代码)
└── matrix.h      # C 头文件 (声明导出函数,供 CGO 识别)

2. 编写 CUDA 内核 (matrix.cu)

这是在 GPU 上运行的核心代码。我们定义一个 matrixMulKernel,每个线程利用自己的坐标 (x, y) 计算结果矩阵中的一个元素。

// matrix.cu
#include <cuda_runtime.h>
#include <stdio.h>

// CUDA Kernel: 每个线程计算 C[row][col] 的值
__global__ void matrixMulKernel(float *a, float *b, float *c, int width) {
    // 根据 Block ID 和 Thread ID 计算当前线程的全局坐标
    int row = blockIdx.y * blockDim.y + threadIdx.y;
    int col = blockIdx.x * blockDim.x + threadIdx.x;

    if (row < width && col < width) {
        float sum = 0;
        // 计算点积
        for (int k = 0; k < width; k++) {
            sum += a[row * width + k] * b[k * width + col];
        }
        c[row * width + col] = sum;
    }
}

extern "C" {
    // 供 Go 调用的 C 包装函数
    // 负责显存分配、数据拷贝和内核启动
    void runMatrixMul(float *h_a, float *h_b, float *h_c, int width) {
        int size = width * width * sizeof(float);
        float *d_a, *d_b, *d_c;

        // 1. 分配 GPU 显存 (Device Memory)
        cudaMalloc((void **)&d_a, size);
        cudaMalloc((void **)&d_b, size);
        cudaMalloc((void **)&d_c, size);

        // 2. 将数据从 Host (CPU内存) 复制到 Device (GPU显存)
        // 这一步通常是性能瓶颈,应尽量减少
        cudaMemcpy(d_a, h_a, size, cudaMemcpyHostToDevice);
        cudaMemcpy(d_b, h_b, size, cudaMemcpyHostToDevice);

        // 3. 定义 Grid 和 Block 维度
        // 每个 Block 包含 16x16 = 256 个线程
        dim3 threadsPerBlock(16, 16);
        // Grid 包含足够多的 Block 以覆盖整个矩阵
        dim3 numBlocks((width + threadsPerBlock.x - 1) / threadsPerBlock.x,
                       (width + threadsPerBlock.y - 1) / threadsPerBlock.y);

        // 4. 启动内核!成千上万个线程开始并行计算
        matrixMulKernel<<<numBlocks, threadsPerBlock>>>(d_a, d_b, d_c, width);

        // 5. 将计算结果从 Device 传回 Host
        cudaMemcpy(h_c, d_c, size, cudaMemcpyDeviceToHost);

        // 6. 释放 GPU 内存
        cudaFree(d_a);
        cudaFree(d_b);
        cudaFree(d_c);
    }
}

3. 定义 C 头文件 (matrix.h)

// matrix.h
#ifndef MATRIX_H
#define MATRIX_H

void runMatrixMul(float *a, float *b, float *c, int width);

#endif

4. 编写 Go 主程序 (main.go)

在 Go 代码中,我们准备数据,并通过 CGO 调用 runMatrixMul。

// go-cuda-cgo-demo/main.go
package main

/*
#cgo LDFLAGS: -L. -lmatrix -L/usr/local/cuda/lib64 -lcudart
#include "matrix.h"
*/
import "C"
import (
    "fmt"
    "math/rand"
    "time"
    "unsafe"
)

const width = 1024 // 矩阵大小 1024x1024,共 100万次计算

func main() {
    size := width * width
    h_a := make([]float32, size)
    h_b := make([]float32, size)
    h_c := make([]float32, size)

    // 初始化矩阵数据
    rand.Seed(time.Now().UnixNano())
    for i := 0; i < size; i++ {
        h_a[i] = rand.Float32()
        h_b[i] = rand.Float32()
    }

    fmt.Printf("Starting Matrix Multiplication (%dx%d) on GPU...\n", width, width)
    start := time.Now()

    // 调用 CUDA 函数
    // 使用 unsafe.Pointer 获取切片的底层数组指针,传递给 C
    C.runMatrixMul(
        (*C.float)(unsafe.Pointer(&h_a[0])),
        (*C.float)(unsafe.Pointer(&h_b[0])),
        (*C.float)(unsafe.Pointer(&h_c[0])),
        C.int(width),
    )

    // 注意:在更复杂的场景中,需要使用 runtime.KeepAlive(h_a)
    // 来确保 Go GC 不会在 CGO 调用期间回收切片内存。

    elapsed := time.Since(start)
    fmt.Printf("Done. Time elapsed: %v\n", elapsed)

    // 简单验证:检查左上角元素
    fmt.Printf("Result[0][0] = %f\n", h_c[0])
}

5. 编译与运行

前提:确保你的机器安装了 NVIDIA Driver 和 CUDA Toolkit。nvcc是CUDA编译器工具链,可以将基于CUDA的代码翻译为GPU机器码。

步骤一:编译 CUDA 代码

nvcc -c matrix.cu -o matrix.o
ar rcs libmatrix.a matrix.o

步骤二:编译 Go 程序

# 链接本地的 libmatrix.a 和系统的 CUDA 运行时库
go build -o gpu-cgo-demo main.go

步骤三:运行

./gpu-cgo-demo

预期输出:

Starting Matrix Multiplication (1024x1024) on GPU...
Done. Time elapsed: 611.815451ms
Result[0][0] = 262.440918

性能优化——从能用到极致

代码跑通只是第一步。Sam 推荐使用 NVIDIA 的 Nsight Systems (nsys) 来进行性能分析。你会发现,虽然 GPU 计算极快,但PCIe 总线的数据传输往往是最大的瓶颈

优化黄金法则:

  1. 减少传输:PCIe 很慢。尽量一次性将所有数据传给 GPU,让其进行多次计算,最后再取回结果。
  2. 利用共享内存 (Shared Memory):Block 内的共享内存比全局显存快得多。在矩阵乘法中,可以利用它实现分块算法 (Tiling),将小块矩阵加载到共享内存中复用,从而大幅减少显存带宽压力。

小结:Gopher 的新武器

Go + CUDA 的组合,为 Go 语言打开了一扇通往高性能计算的大门。它证明了 Go 不仅是编写微服务的利器,同样可以成为驾驭底层硬件、构建计算密集型应用的强大工具。如果你正在处理大规模数据,不妨尝试将计算任务卸载给 GPU,你会发现,那个熟悉的蓝色 Gopher,也能拥有令人惊叹的爆发力。

资料链接:

  • https://www.youtube.com/watch?v=d1R8BS-ccNk
  • https://sam-burns.com/posts/gophercon-25-go-faster/#gophercon-2025-new-york

本文涉及的示例源码可以在这里下载。

附录:告别 CGO?尝试 PureGo 的无缝集成

虽然 CGO 是连接 Go 和 C/C++ 的标准桥梁,但它也带来了编译速度变慢、工具链依赖等问题。有没有一种更“纯粹”的 Go 方式?

答案是有的。借助 PureGo 库,我们可以在不开启 CGO 的情况下,直接加载动态链接库 (.so / .dll) 并调用其中的符号。

让我们看看如何用 PureGo 重写上面的 main.go。

1. 准备动态库

首先,我们需要将 CUDA 代码编译为共享对象 (.so),而不是静态库。

# 编译为共享库 libmatrix.so
nvcc -shared -Xcompiler -fPIC matrix.cu -o libmatrix.so

2. 编写 PureGo 版主程序 (go-cuda-purego-demo/main.go)

// go-cuda-purego-demo/main.go
package main

import (
    "fmt"
    "math/rand"
    "runtime"
    "time"

    "github.com/ebitengine/purego"
)

const width = 1024

func main() {
    // 1. 加载动态库
    // 注意:在运行时,libmatrix.so 和 libcuder.so 必须在 LD_LIBRARY_PATH 中
    libMatrix, err := purego.Dlopen("libmatrix.so", purego.RTLD_NOW|purego.RTLD_GLOBAL)
    if err != nil {
        panic(err)
    }

    // 还需要加载 CUDA 运行时库,因为 libmatrix 依赖它
    _, err = purego.Dlopen("/usr/local/cuda/lib64/libcudart.so", purego.RTLD_NOW|purego.RTLD_GLOBAL)
    if err != nil {
        panic(err)
    }

    // 2. 注册 C 函数符号
    var runMatrixMul func(a, b, c *float32, w int)
    purego.RegisterLibFunc(&runMatrixMul, libMatrix, "runMatrixMul")

    // 3. 准备数据 (与 CGO 版本相同)
    size := width * width
    h_a := make([]float32, size)
    h_b := make([]float32, size)
    h_c := make([]float32, size)

    rand.Seed(time.Now().UnixNano())
    for i := 0; i < size; i++ {
        h_a[i] = rand.Float32()
        h_b[i] = rand.Float32()
    }

    fmt.Println("Starting Matrix Multiplication via PureGo...")
    start := time.Now()

    // 4. 直接调用!无需 CGO 类型转换
    runMatrixMul(&h_a[0], &h_b[0], &h_c[0], width)

    // 5. 极其重要:保持内存存活
    // PureGo 调用是纯汇编实现,Go GC 无法感知堆栈上的指针引用
    // 必须显式保活,否则在计算期间 h_a 等可能被 GC 回收!
    runtime.KeepAlive(h_a)
    runtime.KeepAlive(h_b)
    runtime.KeepAlive(h_c)

    fmt.Printf("Done. Time: %v\n", time.Since(start))
    fmt.Printf("Result[0][0] = %f\n", h_c[0])
}

3. 运行

# 无需 CGO,直接在go-cuda-purego-demo下运行
# 确保当前目录在 LD_LIBRARY_PATH 中
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
CGO_ENABLED=0 go run main.go
Starting Matrix Multiplication via PureGo...
Done. Time: 584.397195ms
Result[0][0] = 260.088806

优势

  • 编译飞快:没有 CGO 的编译开销。
  • 零外部依赖:编译环境不需要安装 GCC 或 CUDA Toolkit,只要运行时环境有 .so 即可。这对于在轻量级 CI/CD 环境中构建分发包非常有用。

注意:PureGo 方案虽然优雅,但也失去了 CGO 的部分类型安全检查,且需要开发者更小心地管理内存生命周期 (runtime.KeepAlive)。


你的“算力”狂想

Go + GPU 的组合,打破了我们对 Go 应用场景的想象边界。在你的业务场景中,有没有哪些计算密集型的任务(比如图像处理、复杂推荐算法、密码学计算)是目前 CPU 跑不动的?你是否会考虑用这种“混合动力”方案来重构它?

欢迎在评论区分享你的脑洞或实战计划! 让我们一起探索 Go 的算力极限。

如果这篇文章为你打开了高性能计算的大门,别忘了点个【赞】和【在看】,并转发给那个天天喊着“CPU 跑满了”的同事!


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

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

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


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

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

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

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

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


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

© 2026, bigwhite. 版权所有.

🔲 ☆

AI 时代,Go 语言会“失宠”还是“封神”?—— GopherCon 2025 圆桌深度复盘

本文永久链接 – https://tonybai.com/2026/01/20/ai-and-go-opportunities-and-challenges

大家好,我是Tony Bai。

在 AI 的滔天巨浪面前,每一位 Go 开发者心中或许都曾闪过一丝不安:Python 似乎统治了一切,我的 Go 语言技能树还值钱吗?AI 会取代我写代码吗?我该如何在这个喧嚣的时代保持清醒?

GopherCon 2025 的压轴圆桌会议上,一场名为“AI 与 Go:机遇与挑战”的深度对话给出了答案。

嘉宾阵容堪称豪华(从左二到右分别是):

  • Ian Cottrell: Google工程师,现从事 AI Agent 开发
  • Katie Hawkman: 前 Go 团队成员,现 Mercari 平台工程师
  • David Soria Parra: Anthropic 技术专家,MCP (Model Context Protocol) 联合创始人
  • Jaana Dogan: 前 Go团队成员,Google Gemini Serving 团队专家, adk-go项目成员
  • Samir Ajmani: Google Go 团队工程总监

他们没有贩卖焦虑,也没有盲目吹捧,而是用冷静、务实的工程师视角,为我们描绘了 Go 在 AI 时代的真实版图。

Go 的新机遇:AI 基础设施的“基石”

当被问及“Go 能提供什么 Python以及其他编程语言 无法提供的价值”时,嘉宾们的回答出奇一致:生产级的可靠性与并发能力。

Samir Ajmani 提出了一个精准的洞察:Go 的崛起得益于云原生时代的爆发,而 AI 正在带来“第二次云原生机遇”。

  • 现状:目前的 AI/ML 基础设施大量依赖 Python,适合快速原型和实验。
  • 痛点:当这些原型需要走向大规模生产,需要处理高并发推理、构建复杂的 Agent 编排、或者实现像 MCP (Model Context Protocol) 这样需要高度可靠性的协议时,Python 的动态特性和性能瓶颈开始显现。
  • Go 的位置:Go 语言天生的高并发模型、静态类型安全、以及构建大规模分布式系统的基因,使其成为构建 AI 生产基础设施(Serving, Orchestration, Agent Protocols)的完美选择。

Katie 分享了一个真实案例:她在黑客马拉松中选择用 Go 而非 TypeScript 来编写 MCP Server,因为 Go 的代码在处理复杂协议逻辑时更易读、更易维护。

David(Anthropic)就个人经验和观察,认为Go 是目前AI最擅长生成的语言代码之一,这也是Go的一大优势!

Python 也许是 AI 的“训练语言”,但 Go 有望成为 AI 的“运行语言”

职业焦虑:AI 会取代我们吗?

面对“AI 取代程序员”的言论,嘉宾们的态度是——“这只是另一种生产力工具,它改变了工作方式,但提升了人的价值。”

  • Samir Ajmani:未来的软件构建方式可能会变成“组件组装”。但这依然需要懂系统设计、安全性和可靠性的专业人士来构建这些高质量的组件。对于初级开发者,门槛确实变高了(简单的代码生成不再是技能壁垒),但对于具备系统思维的工程师,这是最好的时代。
  • Jaana Dogan (Google):她提出了一个令人耳目一新的视角——“代码写得快了,不仅没让我失业,反而让我更强大了。” AI 极大地缩短了编码时间,这意味着工程师可以更快地去“连接点” (connect the dots):将孤立的组件串联成系统,与更多人协作,验证更多设计想法。个人的产出能力被放大了,你不再是一个单纯的“螺丝钉制造者”,而更容易成为一名“系统架构师”。
  • David Suryapara (Anthropic):作为一名非 Go 核心开发者,David 的观察更为冷静。他认为,纯粹的“代码编写”技能(例如熟练背诵 API、手写 CSS)确实面临贬值。但核心工程能力——如拆解复杂需求、设计分布式系统、处理边缘情况——将变得前所未有的重要。 AI 抬高了入行的地板,但也让那些拥有深厚解决问题能力的工程师变得更加不可替代。
  • Katie Hawkman:写代码从来不是工作中“最难”的部分,而是“最有趣”的部分。真正的难点在于——如何渐进式交付?如何设计良好的 UX?如何优化系统性能?这些是 AI 短期内无法完全替代的工程智慧。
  • Ian Cottrell:我有 40 年的开发经验,每一次生产力工具的飞跃(从汇编到 C,从 IDE 到自动补全),人们都说“不需要程序员了”。结果呢?我们的需求量反而更大了。我们只是在提升期望值,尝试解决更难的问题。

不要试图成为每一个 AI 工具的专家。选择一个工具(如 Cursor 或 Claude Code),深入掌握它,让它服务于你的工作流,而不是被它淹没。

理性审视:算力、能源与负责任的 AI

主持人提出了一个尖锐的问题:在区块链曾因高能耗饱受诟病之后,我们该如何理性看待 AI 巨大的算力和能源消耗?作为开发者,我们该如何权衡使用 AI 工具的成本?

嘉宾们的回答,揭示了工程优化在 AI 时代的巨大潜力:

  • Samir Ajmani (Google) 分享了一个令人振奋的实验:Go 团队尝试将 MCP 支持集成到 Go 语言服务器 (LSP) 中。结果发现,当 AI 能够直接调用精确的工具(Tools)而不是在那“空想”时,任务完成率提高了,延迟降低了,最重要的是——Token 消耗量减少了近 50%。 这意味着,通过优秀的工程工具(如 Go),我们可以显著降低 AI 的运行成本和碳排放。
  • Jaana Dogan (Google) 认为我们正处于优化的早期阶段。就像当年的数据库优化一样,模型推理 (Inference) 的效率优化将是接下来的重头戏。缓存、量化、专用硬件,这些工程手段将大幅抵消模型增长带来的成本。
  • David Suryapara (Anthropic) 提到了“小模型与蒸馏”。我们不需要每次都动用最昂贵、最慢的“超大模型”来解决所有问题。未来,针对特定领域(如代码生成)进行微调和蒸馏的小模型,将在效能和成本之间找到完美的平衡点。

不要盲目堆砌算力。“负责任的 AI”不仅是道德要求,更是工程优化的必然方向。 用更少的 Token 做更多的事,这本身就是 Go 开发者擅长的“资源优化”技能的延伸。

务实派的生存指南:过滤噪音,回归本质

在 AI 炒作的喧嚣中,如何保持清醒?

  1. 从“小”开始:不要被“AGI 即将到来”的宏大叙事吓倒。像 Katie 建议的那样,承认自己是初学者,哪怕是 MCP 的创始人也说“现在没有所谓的专家”。放下包袱,去尝试写一个简单的 Agent,去用 Go 写一个 MCP Server。
  2. 关注“确定性”:Jaana 和 Ian 都提到,AI 模型本质上是概率性的(非确定性),而工程系统需要确定性。Go 语言强大的静态分析、测试工具链和类型系统,是约束 AI 幻觉、构建可靠系统的最佳防线。用 Go 的“确定性”去包裹 AI 的“不确定性”,是未来的核心工程模式之一。
  3. 解决实际问题:不要为了 AI 而 AI。如果老板让你“加点 AI 进去”,试着去寻找那些真正能通过 AI 提升效率的痛点(比如自动化文档更新、复杂日志分析),而不是生搬硬套。

小结:Go 社区的“绿地”时刻

这场圆桌会议传递出的最强烈信号是:乐观

我们正处于一个类似于 2013 年 Docker 诞生前夜的时刻。AI 领域的“Kubernetes”、“Prometheus”还没有被写出来。这片巨大的空白,正是 Go 开发者施展拳脚的“绿地” (Greenfield)。

正如 Samir 所言:

“如果我想让 AI 真正能够与现实世界进行交易(比如订购 Pizza 并且真的送到),这中间需要大量的、可靠的基础设施。而 Go,是构建这一层的绝佳语言。”

所以,Gopher 们,别慌。带上你的并发模型,带上你的工程智慧,去构建 AI 时代的钢铁地基吧。

资料链接:https://www.youtube.com/watch?v=r40Mwdvg38M


你的 AI 实践

听了这些顶级专家的观点,你是否对 Go 在 AI 时代的未来更有信心了?在你目前的开发工作中,是否已经开始尝试用 Go 构建 AI 应用或基础设施?你认为 Go 在 AI 领域最大的短板是什么?

欢迎在评论区分享你的实战经验或困惑!让我们一起探索 Go + AI 的无限可能。

如果这篇文章为你扫除了职业焦虑,别忘了点个【赞】和【在看】,并转发给身边迷茫的 Gopher 朋友!


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

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

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


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

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

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

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

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


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

© 2026, bigwhite. 版权所有.

🔲 ☆

谁在裸泳?GEO概念股塌房背后的逻辑:游资造势收割散户 vs 巨头构建交易闭环,三张图看懂真实商业壁垒|数据织物、电商、欧洲投资者、市场板块

2026年开年A股AI第一波题材:GEO概念的起伏与真相

大家好,欢迎收听老范讲故事的YouTube频道。今天咱们来讲一讲2026年开年,A股的AI第一波题材——GEO为什么会塌房、概念炒作的时间线、澄清公告的解读,以及GEO到底是干嘛的。

中国AI概念股的炒作难点

中国AI概念股其实不太好炒。为什么?

  • 芯片企业:都是刚上市,市值很高,而且都还严重的亏损。这种刚上市的企业都属于是有独立行情,很难炒得动。因为你要这个时候去收割的话,里面会有很多的国资去收拾你的。为什么?因为能够给上市公司做保荐的,一般都是大国资、大央企,你要让这些人亏了钱的话,那真的不会有好果子吃。
  • 大模型厂商:除了新上市这两个,也是不太方便炒,剩下都是大厂,阿里、百度、腾讯什么这些,这都是炒不动的。
  • AI Agent:Manus刚跑了,其他的老的老、小的小。要不就特大上市公司,已经是大厂了;要不就现在还在垂死挣扎,就是还没上市,也没有法搞。

所以这次好不容易逮了一个新话题——GEO,赶快炒一波。这一波实际上从12月底就开始了,12月31号开始了,到1月14号迎来了这一波的GEO的最后行情的终结。

GEO行情的时间线与逻辑

为什么从12月底就开始了?因为谷歌的行情是12月底突然就爆了。谷歌爆完了以后,不光是TPU怎么样了,而是广告又行了。大家发现,原来AI并没有杀死广告,广告收入在快速上涨。

那么GEO就成为了新的热点。原因也很简单,因为AI搜索的流量已经极大的侵占了传统搜索的势力范围,以后大家都是通过AI来获得结果的,你做做这个传统的肯定没戏了。马斯克1月10号还宣布说,要公开X的推荐算法,这更进一步的加强了Grok的概念,因为X现在的新推荐算法就完完全全是在Grok上做的。那未来一定是做好GEO的人才有机会。在这样的情况下,妖股就出现了。

“妖股”盘点:毫无道理的暴涨

大家注意我讲的妖股。什么叫妖股?就是突然暴涨,涨的还没有任何道理。

  • 蓝色光标:2025年12月31日到2026年1月12号,涨了114.79%,翻倍了。
  • 易点天下:一月多个20%的涨停板,就砰砰砰往上涨。
  • 浙文互联:1月有3天的两连板。
  • 引力传媒:7天6板,涨了84.64%。
  • 天龙集团:10天累计涨了90%。

这都属于是中国号称是GEO概念股,他们都自己宣称说我们有大模型、我们有GEO优化的AI agent、有GEO优化的这种服务在卖,就把他们直接吹上去了。

澄清函纷至沓来:概念的破灭

然后到1月14号、15号的时候,澄清函就纷至沓来了。在中国,你如果股票暴涨,你最好是真的有业绩;如果你没有业绩的话,你就要出来写澄清函。所以中国的股市是不能暴涨暴跌的。所谓澄清函就是“否认三连”:不是我,我没有,别瞎说。

  • 蓝色光标:发了一个澄清函:AI驱动的收入占比很小,对业绩不构成实质影响;GRO业务处于布局阶段,尚未形成稳定的盈利模式;股价短期涨幅过高,偏离基本面,存在快速回落风险。
  • 易点天下:比蓝色光标更狠,他直接停牌了。他说我申请停牌自查,然后发了一个澄清公告,明确不涉及GEO业务,未因AI产生额外的收入,被列入GEO概念股完全是市场误解,核心业务是帮助跨境电商去做营销的。
  • 浙文互联:他说我们倒是有,他们叫Hochi GEO,GEO智能体确实已经上线了,但是尚未形成收入,还不挣钱,无成熟的盈利模式,市场认可度存在不确定性,主营业务是数智营销服务未发生重大变化。
  • 引力传媒:说GEO业务仍处在组建筹备阶段,无成熟商业模式,未形成相关收入,主营业务仍然是广告代理,它实际上是替人投广告的。

A股游资炒作套路揭秘

A股其实总是有很多很多的行情,这些行情大多都是怎么来的?都是游资在发起。A股专门有一种特殊的现象就是游资,他不是这种国营的,也不是量化,他就是一个相对来说比较灰色的一帮人,他们去发起各种行情。

1. 寻找概念

国内其实比较难创造出概念来,大部分的概念都是国际概念,我们就抄一个。例如:

  • TPU概念:谷歌芯片供应链。
  • Rubin概念:英伟达最新一代GPU的中国供应商。
  • 擎天柱概念:特斯拉机器人国内零部件供应商。
  • 脑机接口概念OpenAI硬件概念等。

2. 筛选公司

找到概念以后,就开始生拉硬套,在各个上市公司里面去找合适的这种公司往上去套这个概念去。找什么样的公司合适?

  • 要找小盘股,大盘股拉不动。
  • 最好与之相关一点,上市公司也愿意附庸风雅,发新闻稿配合。

3. 炒作流程

他们整体的流程叫:概念筛选 -> 业务包装 -> 舆论造势 -> 拉高出货。这就是一个标准的在中国炒概念的一个流程。

GEO到底是什么?从SEO说起

那GEO这是个好概念。谷歌已经证明了广告不会塌方,还是王者。GEO(Generative Engine Optimization)其实跟SEO(Search Engine Optimization)类似,国外有的时候叫AEO(AI Engine Optimization)。

SEO(搜索引擎优化)

SEO是让内容或者是商品服务对搜索引擎更加优化,更加友好,可以被更多的搜索热词命中。SEO里头有两部分:

  • 白色部分(合规):做关键词贴合,做更多热词命中,做格式调整,让它更适合SEO。
  • 灰色部分(排名):提升搜索排名的位置。这是相对灰色的,谷歌、百度都不希望你做,因为这影响人家卖广告挣钱。

GEO(生成式引擎优化)

GEO就是AIGC内容生成引擎的优化,让内容更容易被AI聊天工具复述出来。GEO的效果要比SEO更加不可控一些:

  • 无法调优:SEO可以针对热词调优,且结果是一个列表;GEO大模型每次只吐出一个结果,没有排序,你不知道距离被吐出来还有多远。
  • 长尾效应:GEO的内容特别长尾,热词不集中。
  • 过滤机制:针对热词做GEO会被大模型直接过滤掉,如果词跟你没关系非要蹭,信息抽取时直接就被过滤了。

关于GEO提升营收的“都市传说”

很多人在小红书上说,通过GEO提升了多少曝光和营收。这纯属都市传说。现在还没有办法去证明GEO确实能够让营收快速上升。那些卖GEO课程的人举的例子无法被证实。

虽然不排除有突出的效果,但是无法量化“做之前”和“做之后”的区别。SEO可以通过来源分析流量,而GEO的价值目前无法衡量,广告平台目前也没有开始对GEO相关的广告投放。

GEO实操指南:如何让大模型读懂你

GEO到底怎么做?这其实也是个信息系统问题:信息的输入、处理和输出。GEO要优化的是信息输入的部分。大模型摄取信息分三次:预训练、强化学习、RAG(检索辅助生成)。GEO主要针对的是RAG这个过程。

基础设置

  1. robots.txt:必须允许OpenAI、Bing等爬虫抓取你的网站。
  2. llms.txt:这是一个新标准文件,告诉大模型应该怎么用你的网站内容,哪个页面要、哪个不要。

大模型喜欢什么样的内容?

我们要把内容写成大模型喜欢的样子:

  • 实体优先:把你是谁、卖什么、服务地区、价格、条款写清楚。
  • 原子化事实:清晰的小节,列表、表格、FAQ(问答)。
  • 结构化数据:使用Key-Value(键值对)形式,例如“地址:北京市昌平区…”。
  • 逻辑清晰:因为A导致了B变成C,这种逻辑关系大模型喜欢。
  • 单一事实来源:价格、库存、活动、规则写在一个页面里,不要分散。
  • 时间戳和版本:生效时间、截止时间、更新日期。
  • 引用和凭证:提供资质、第三方评价、公开证据,并附上链接,通过大模型的核查。
  • JSON格式/API:最好直接提供JSON格式的数据交互API,或者提供MCP(Model Context Protocol),让大模型直接调用。
  • 可验证的身份和一致性:统一的商家名称、门店ID、电话等,跨平台必须一致。

谁最应该做GEO?

目前最应该去做GEO的是那些高客单价、强信息不对称、决策链条特别长的品类。因为客户会用大量的时间跟AI聊天来明确决策。

  • 旅游:信息不对称,行程安排复杂。
  • 保险
  • B2B软件
  • 医疗服务

GEO的商业困境:谁能赚到钱?

为什么A股公司纷纷澄清?因为GEO生意目前很难做。广告生意有三个要素,GEO目前都无法满足:

  1. 归因:AI还没形成交易闭环,很难证明交易是AI带来的。
  2. 可规模化:AI推荐规模不可控,非常长尾。
  3. 可持续性:AI引擎机制(大模型、搜索、排序)每天都在变。

最终谁能挣钱?

最终还是广告平台(谷歌、Meta、字节、阿里、腾讯)。当广告平台开始接受投放和竞价排名,并实现交易闭环(如谷歌UCP、阿里千问APP打通淘宝/飞猪/饿了么)时,这条路才能走通。

GEO服务商(如蓝标等)只能赚取咨询费和数据加工费,天花板较低,最终还是要靠代理投放挣钱。

总结

A股开年的第一波AI概念行情就翻车了。在中国的股市上,各种概念满天飞,但大多是游资割韭菜,不要太认真。最终的大钱只有谷歌这些平台才能挣到。其他的GEO概念股只是物料制作和投放代理,赚点边边角角的钱就完事了。

不过,GEO确实是有效果的,只是效果比较难以衡量和评估。对于提供内容和服务的人,还是建议好好的把GEO做起来,顺应时代潮流。

🔲 ☆

Piccolo P2P 镜像分发

我们遇到的 Harbor 的另外一个问题是 image 的下载瓶颈。在容灾的时候,我们需要在短时间内启动几万个容器。Harbor 这里就成了瓶颈,抛开所有的数据库和文件系统的瓶颈不说,网络这里就需要 Tib 级别的带宽。这是不现实的。

用户构建的 image 质量参差不齐,大于 10GiB 的 image 比比皆是,尽管这些 image 都有很大的优化空间,但是期望所有的用户都按照构建 image 的最佳实践1进行优化,也是不现实的。

于是问题就成了:如何才能够大量的 worker 节点迅速扩容上万的容器?

此外,在平时,遇到工作日多个系统发布的时候,或者整点的时候运行定时任务(定时任务每次都会下载 image),harbor 的压力也非常大,经常满载运行。如果能解决这个问题,平时的负载问题也可以缓解。

之前介绍过 Spegel 的下载方案2,Spegel 的想法很好,基本思想是,先去其他已经存在这个 image 的机器上去下载,如果找不到,再 fallback 到 Harbor 下载。可惜的是,Spegel 把 P2P 下载和 P2P 服务发现混为一谈了,导致服务发现的性能极低。

什么意思呢?去其他的机器上下载 image,需要的一个信息是:哪一个机器有这个 image?这就是下载源的服务发现。这个服务发现和 P2P 本质上没有什么关系,用什么都可以。P2P 技术解决的是下载上的瓶颈,只有能有一个方法记录每一个机器上现在都有什么 image 就可以,用 Etcd 也可以,不一定也得用 P2P 形式的。

但是 Spegel 这里是用 P2P (libp2p)做的服务发现。在我看来这是完全没有必要的。我们在实际的部署中遇到的问题有:

  • 服务发现的 latency 高,现在的配置是 30s,还是会有超时,P2P 本质上是在一个不稳定的分布式网络中寻找一个资源,效率不高;
  • 成功率低,在 P2P 网络里面,能否发现一个 key 是概率性问题,不是确定的3
  • 删除 image 不会分发到 p2p 网络中,必须有访问事件得到 404 然后触发清除,这是 libp2p 本身的设计决定的,这又会导致服务发现的错误率高;

在实际的部署中,Spegel 的缓存命中率在 25% 左右。

当然,使用 P2P 做服务发现也是有好处的,好处就是部署简单,不需要额外的存储依赖。只不过这个好处和它带来的问题相比就微不足道了。

我觉得如果解决服务发现的问题,使用 P2P 下载的方法,是可以解决资源的瓶颈问题的。

所以设计的方案是:使用中心化的,高性能的服务发现,去中心化的 P2P image 下载。

原本 Spegel 的代码,服务发现层是独立的模块,所以我从 fork 它的代码来添加一个新的服务发现方式开始。但是随着修改,发现问题越来越多,比如 subscribe containerd events 的时候没有正确 defer 关闭,没处理好 containerd 重启的情况,等等,最后开始了一个独立的仓库,叫 Piccolo。代码4依然是开源的,但是还没时间写文档和注释,这篇博客先写一下原理。

项目主要分两部分:

Pi – 安装在每台机器上的 daemonset,负责:

  • 作为 containerd 的本地 mirror,当 containerd 需要下载 image 的时候,会先尝试本地的 Pi 端口,如果得到 404 (或者 5xx),再尝试下一个下载 mirror,一般是 dragonfly,最后是 harbor source;
  • 连接本地的 containerd,跟踪本地的 image 状态,本地的 image 增加或者减少,报告给 Piccolo server;
  • 连接本地的 containerd,其他的 Pi 发送来下载 image 请求的时候,上传本地有的 image。

Piccolo Server 是服务发现的源,全局只有一组(几个实例就够用了),提供 3 个接口:

  • Advertise:其他 Pi 上报的 image 列表,存储到 MySQL;
  • Findkey:Pi 来询问一个 image(实际上是 image 的 manifest 和 blob)在哪里有的时候,回复地址列表;
  • Sync:其他 Pi 可以用这个接口做全量同步(定时,以保证 image 总是最终一致的);
Piccolo 的架构图

部署之后,97% 的下载请求可以在 Pi 完成而不必请求 Harbor。

Harbor 的流量对比,绿色的线是逐步发布 Piccolo 的流量,黄色线是之前的日常流量。

从这个监控可以看出,随着 Piccolo 的发布,Harbor 的流量骤降。而且每个小时的峰值流量也几乎没有了。

Pi 部署在每一个机器上,使用的资源也非常少,平均 CPU 用了一个 core 的不到 1%,可以忽略不计。平均内存用了 22M 左右,也可以忽略不计。

Piccolo 为集群提供了大约 8Tib 的下载带宽,没有消耗额外的资源,几乎是免费的 8Tib 带宽。

Piccolo server 方面,性能也很高,一台 8C8G 的 instance 足以支撑 5 万个 Pi (实际的物理机 worker 节点)。秘诀就是注重细节的性能优化。

比如全量同步的资源消耗较大,一起部署的机器会定时发送 keeplive,通过对这些定时执行的 API 加随机偏移,可以保持这些 API 的频率几乎是均匀的,解决了资源的峰值问题。

服务发现的核心,是用一张 MySQL 表,存储了 blob 和 IP 的对应关系。服务发现请求主要是通过这张表的查询完成的。Piccolo 支持把不同的 group(同一个 group 的 Pi 可以互相发现,不同的 group 的 Pi 不可以互相发现。其实这个功能也可以通过部署多个 Piccolo Server 来实现)放到不同的数据库中,加上索引优化(极致的索引优化,所有的查询都是 index-only 的),每一个库 2千万的数据,请求在 200 QPS,耗时在 10ms 以内,已经足够使用了。

Piccolo server 在服务发现接口返回的时候,会根据请求者的 IP 地址,把所有的资源拥有者的 IP,根据和请求者的 IP 相似度(距离)排序,返回。这样 Pi 在下载的时候,会从距离和它最近的邻居开始尝试,这样可以最大程度减少跨网络设备的带宽流量。

所有的 API 接口都有重试和指数时间退让,这样在大规模部署的时候,可以分散一些请求,不至于大家一起失败。

在高可用方面,由于 Piccolo server 是无状态的,所以部署多个实例即可。在预防未知的 bug 上,系统的每一个阶段都是可以降级的:

  • 如果 containerd 从 Pi 下载失败,会 fallback 到下一个下载源;
  • 如果 Pi 从一个 Pi 下载失败,会继续尝试下一个 Pi,直到超时;
  • 如果 Pi 访问 Piccolo 失败,会等待并重试,直到超时;

  1. 这里是之前 blog 过的一些技巧 Docker 镜像构建的一些技巧 ↩
  2. Spegel 镜像分发介绍,也讨论了一些其他的可能方案,比如 dragonfly,或者 lazy loading ↩
  3. https://en.wikipedia.org/wiki/Kademlia ↩
  4. Github 地址:https://github.com/laixintao/piccolo ↩
🔲 ☆

当机器开始“剁手”:详解 Google UCP 与 Agentic Commerce 的架构革命

本文永久链接 – https://tonybai.com/2026/01/14/google-ucp-agentic-commerce-architecture-revolution

大家好,我是Tony Bai。

想象一下,未来的某一天,你们公司的电商网站流量突然暴涨了 1000 倍。

但奇怪的是,后台数据显示 PageView(页面浏览量)几乎为零,热力图一片空白,也没有任何用户在点击你的促销弹窗。

这并不是遭受了 DDoS 攻击,而是你迎来了第一批“机器顾客”

我们正在从“人机交互”的电商时代,跨入“Agentic Commerce(智能体商业)”的新纪元。在这个时代,代替人类下单的,是运行在手机、云端或眼镜里的 AI Agent(智能体)

如果你是技术负责人,你可能会感到背脊发凉:

现有的这套为人类设计的、充满图片、广告和前端渲染的电商基建,对于“硅基生物”来说,效率低得令人发指。

为了迎接这场变革,Google 近期开源了 UCP (Universal Commerce Protocol),微软研究院在去年也前瞻性地发布了 Agentic Economy报告,Aqfer 提出了 AIO (AI Agent Optimization) 概念。

今天,我们就结合这三份重磅资料,从协议、基建、经济三个维度,深度剖析这场正在发生的架构革命。

第一性原理:为什么我们需要 Agentic Commerce?

根据微软研究院(Microsoft Research)的报告,Agentic Commerce 的爆发并非偶然,而是经济学第一性原理的必然推论。

传统的电商交易链路充满了“通信摩擦(Communication Frictions)”

  • 人类: 搜索 -> 筛选 -> 比价 -> 阅读评论 -> 填表 -> 支付。
  • 摩擦: 每一个环节都在消耗人类有限的注意力认知带宽

AI 智能体的出现,本质上是在消除这些摩擦

未来的购物模式将简化为:意图 -> 交易

用户只需说:“帮我买一个去日本旅游用的轻便行李箱,预算 500 元内,要耐摔的。”

接下来的搜索、比价、看评论、下单、支付,全部由 Assistant Agent(助理智能体) 和商家的 Service Agent(服务智能体) 在后台通过协议谈判完成。

这不仅是用户体验的升级,更是交易效率的指数级跃迁

技术基座:Google UCP 协议详解

然而,理想很丰满,现实很骨感。目前的 Agent 购物面临一个巨大的工程难题$N \times N$ 的集成灾难

每个商家都有自己的私有 API,Agent 不可能适配全天下所有的电商接口。

为了解决这个问题,Google 提出了 UCP (Universal Commerce Protocol,通用商业协议)

你可以把 UCP 理解为电商界的“USB 接口”。它定义了一套标准化的语言,让消费者智能体(Consumer Agent)和商家后端(Business Backend)能够直接对话。

UCP 的核心架构设计

  1. 标准化发现 (Discovery)
    类似于 robots.txt,商家只需在 .well-known/ucp 路径下发布一个 JSON 清单,声明:“我是卖花的,我支持搜索、加购和 Google Pay。” Agent 读到这个文件,就知道了交互规则。

  2. 原子化能力 (Capabilities)
    UCP 定义了一组标准的原语(Primitives),如 ProductDiscovery(商品发现)、Cart(购物车)、Checkout(结账)。这些原语是跨平台的,无论是 Amazon 还是独立站,语义都一样。

  3. 灵活的传输层 (Transport)
    UCP 不仅支持传统的 REST API,还原生支持 MCP (Model Context Protocol)
    这意味着,你的 UCP 服务可以直接作为一个 MCP Server 挂载到 Claude 或 Gemini 中,让大模型“天生”就具备操作你店铺的能力。

Agent 看到的不再是 HTML,而是干净的 JSON:

// UCP Checkout Response Example
{
  "ucp": {
    "version": "2026-01-11",
    "services": { "dev.ucp.shopping": { "version": "2026-01-11", "spec": "https://ucp.dev/specs/shopping", "rest": { "schema": "https://ucp.dev/services/shopping/openapi.json", "endpoint": "http://localhost:8182/" } } },
    "capabilities": [
      { "name": "dev.ucp.shopping.checkout", "version": "2026-01-11", "spec": "https://ucp.dev/specs/shopping/checkout", "schema": "https://ucp.dev/schemas/shopping/checkout.json" },
      { "name": "dev.ucp.shopping.discount", "version": "2026-01-11", "spec": "https://ucp.dev/specs/shopping/discount", "schema": "https://ucp.dev/schemas/shopping/discount.json", "extends": "dev.ucp.shopping.checkout" },
      { "name": "dev.ucp.shopping.fulfillment", "version": "2026-01-11", "spec": "https://ucp.dev/specs/shopping/fulfillment", "schema": "https://ucp.dev/schemas/shopping/fulfillment.json", "extends": "dev.ucp.shopping.checkout" }
    ]
  },
  "payment": {
    "handlers": [
      { "id": "shop_pay", "name": "com.shopify.shop_pay", "version": "2026-01-11", "spec": "https://shopify.dev/ucp/handlers/shop_pay", "config_schema": "https://shopify.dev/ucp/handlers/shop_pay/config.json", "instrument_schemas": [ "https://shopify.dev/ucp/handlers/shop_pay/instrument.json" ], "config": { "shop_id": "d124d01c-3386-4c58-bc58-671b705e19ff" } },
      { "id": "google_pay", "name": "google.pay", "version": "2026-01-11", "spec": "https://example.com/spec", "config_schema": "https://example.com/schema", "instrument_schemas": [  "https://ucp.dev/schemas/shopping/types/gpay_card_payment_instrument.json"
 ], "config": { "api_version": 2, "api_version_minor": 0, "merchant_info": { "merchant_name": "Flower Shop", "merchant_id": "TEST", "merchant_origin": "localhost" }, "allowed_payment_methods": [ { "type": "CARD", "parameters": { "allowedAuthMethods": [ "PAN_ONLY", "CRYPTOGRAM_3DS" ], "allowedCardNetworks": [ "VISA", "MASTERCARD" ] }, "tokenization_specification": [ { "type": "PAYMENT_GATEWAY", "parameters": [ { "gateway": "example", "gatewayMerchantId": "exampleGatewayMerchantId" } ] } ] } ] } },
      { "id": "mock_payment_handler", "name": "dev.ucp.mock_payment", "version": "2026-01-11", "spec": "https://ucp.dev/specs/mock", "config_schema": "https://ucp.dev/schemas/mock.json", "instrument_schemas": [ "https://ucp.dev/schemas/shopping/types/card_payment_instrument.json" ], "config": { "supported_tokens": [ "success_token", "fail_token" ] } }
    ]
  }
}

基础设施危机:“海啸级”查询与营销失效

当 Agent 能够读懂 UCP 协议后,商家的技术架构将面临前所未有的挑战。Aqfer 在其白皮书中发出了警告:你的基础设施准备好迎接“机器海啸”了吗?

流量的量级跃迁

人类逛淘宝,一分钟看 5 个商品就累了。

AI 智能体为了帮主人找到“最优解”,可能会在几毫秒内扫描 1000 个 SKU,实时比对全网价格和库存。

你的 Read API QPS 可能会暴涨 100倍 – 1000倍。传统的缓存策略可能失效,因为 Agent 需要毫秒级的实时库存(Real-time Inventory)准确性。

营销逻辑的崩塌

这是最让市场部绝望的一点:AI 智能体对“情绪”免疫。

你在详情页上精心设计的品牌故事、氛围感图片、促销倒计时,对于 LLM 来说只是无意义的 Token 噪音。

Agent 只关心:Data (数据)

  • 价格是多少?(精确数字)
  • 材质是什么?(结构化参数)
  • 物流几天到?(SLA 承诺)

从 SEO 到 AIO (AI Agent Optimization)

未来的流量入口不再是搜索引擎,而是 AI 智能体。

如果你想被 Agent 选中,你需要的不是 SEO(针对关键词优化),而是 AIO(针对智能体优化)

Data is the UI. 你的商品数据必须是清洁的、结构化的、向量友好的。如果你还在用图片存参数表,你的商品在 Agent 眼里就是隐形的。

未来推演:围墙花园 vs. 开放网络

微软研究院的报告指出了两种可能的终局:

  • 路径 A:Agentic Walled Gardens(智能体围墙花园)
    OpenAI、Google、Apple 建立自己的“智能体 App Store”。商家必须适配它们的私有协议才能被其 Agent 访问。这会形成新的垄断。

  • 路径 B:Web of Agents(智能体开放网络)
    基于 UCPMCP 这样的开放标准,任何商家的 Service Agent 都可以和消费者的 Assistant Agent 自由交易,无需经过中心化平台。

这就是为什么 Google 要急于开源 UCP标准协议。协议之争,将决定未来十年的互联网商业格局。

小结:为“机器客户”重构系统

Agentic Commerce 不仅仅是一个技术热词,它是一场生产关系的重构

作为架构师,你的使命正在发生变化:

从“为人类构建漂亮的 UI”,转变为“为机器构建健壮的 API”

不要等到你的竞争对手已经被 AI 智能体“自动下单”买空了库存,你还在研究 Landing Page 的按钮颜色。

拥抱协议,结构化数据,迎接那个“万物皆可被 Agent 调用”的未来。

参考资料

  • The Agentic Economy – https://arxiv.org/abs/2505.15799
  • Under the Hood: Universal Commerce Protocol (UCP) – https://developers.googleblog.com/under-the-hood-universal-commerce-protocol-ucp/
  • Universal Commerce Protocol官网 – https://ucp.dev/
  • The Age of Agentic Commerce: When Machines Become Your Customers – https://aqfer.com/wp-content/uploads/2025/09/AgenticCommerce_9.3.25_final_v1.pdf

你的“机器顾客”准备好了吗?

Agentic Commerce 的未来听起来既科幻又紧迫。如果你的应用突然迎来了一波 AI Agent 的访问,你的 API 扛得住吗?你认为未来的电商是会被巨头垄断,还是通过 UCP 走向开放?

欢迎在评论区分享你的脑洞或担忧! 让我们一起为即将到来的“机器时代”做好准备。

如果这篇文章让你对未来的电商架构有了全新的认识,别忘了点个【赞】和【在看】,并转发给你的产品经理和老板,告诉他们:变天了!


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

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

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


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

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

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

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

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


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

© 2026, bigwhite. 版权所有.

❌