Hacker News 中文摘要

RSS订阅

Claude 能测量即可提速 -- Once Claude can measure something, it can make it faster

今年8月,我们用一个为期两周的冲刺,把 claude.ai 和 Claude 桌面应用(Claude desktop app)的核心体验提速约3倍。用户此前一直反馈它慢,他们说得没错。整个行动只靠一个 Slack 频道推进,每个讨论串里都有 Claude。我们聚焦占用户活动95%的四条路径:全新加载 claude.ai 时,第75百分位下可输入页面的时间从3.1秒降到0.55秒,启动新的 Claude Code 会话从0.8秒降到0.3秒,加载 Claude Cowork 云会话从2.6秒降到0.73秒。

正文

今年八月,我们在为期两周的冲刺里把 claude.ai 和 Claude 桌面应用的核心体验提升了约 3 倍。用户一直说它慢,他们说得对。所有工作都在一个 Slack 频道里推进,每个线程里都有 Claude。

我们聚焦四条占用户活动 95% 的路径。在 75 分位,全新加载 claude.ai 到可输入页面的时间从 3.1 秒降到 0.55 秒,启动新的 Claude Code 会话从 0.8 秒降到 0.3 秒,加载 Claude Cowork 云会话从 2.6 秒降到 0.73 秒。总计,我们估计每天省下数万小时的用户等待。

我们用了 Claude Tag(beta),运行一个内部研究模型,大致相当于 Opus 5.5。Claude 找到瓶颈、建立基准、交付改进,并盯着每次部署。我们通过设定目标、做取舍、批准每个变更来掌舵。靠这种方式,我们合并了三千多个变更,没有一次面向客户的事故或回滚。下面是我们交付了什么、如何衡量,以及我们与 Claude 一起安全推进所建立的循环。

简报

冲刺前,我们创建了一个 Slack 频道,并设置以下长期指令:

@Claude 你的工作是推动所有与 claude.ai 网站和桌面应用性能相关的事。你的职责包括监控部署中的性能回退、评估现有遥测的准确性和全面性、维护精心整理的可观测性仪表盘、主动为观察到的问题和低垂果实实施解决方案、提出性能项目机会,并与人类队友沟通。[…]

这个频道的最终目标是让你尽可能自主,但今天我们知道那还不可能。

我们让 Claude 通过 Datadog MCP 服务器分析使用数据。它识别出四条影响最大的用户路径:启动应用、开始对话、加载已有对话、发送消息。在 Web 和桌面端、跨产品之间,这些路径变成十三项不同的测量。为了建立基线,我们不断添加埋点,直到它们可以直接比较:每项都从用户交互开始,到结果渲染完成结束,并区分客户端和服务端工作。

我们用大约二十个手挑项目启动冲刺,每个项目针对一条具体路径。Claude 估算每个项目的影响,单位是毫秒,我们汇总这些估算来设定冲刺目标。有些项目相当大,但我们觉得两周内大概能完成大部分。

第三天,我们命中了十三项目标中的十二项。

计划内的项目提前落地。为了更快启动,我们把静态输入框烘焙进 HTML,让用户在 React 初始化期间就能输入;还预编译了 V8 代码缓存,这样桌面壳的主进程不用从头重新编译。为了更快导航,我们在对话之间保持输入框挂载,在用户悬停会话时预取,并把侧边栏重渲染削减了 90%。

我们也留出空间,让 Claude 自己识别机会、提出新工作流。这些工作流很快长成完整的项目,远超最初目标。于是我们设定新目标,然后寻找更多可衡量的东西:

@Claude 我们最终资助了原始项目列表里的几乎每个项目,还更多。让我们刷新一下 […] 我们还没探索什么,还能在哪些地方爬坡,现在最大的机会在哪?[…] 我对 WACKY 想法持开放态度

一切都能爬坡

从一开始,我们就知道想比部署节奏更快地迭代。Claude 可以异步工作很多小时,甚至过夜,我们希望让它不用等现场数据就能验证原型。为此,我们寻找其他在实验室里衡量性能的方法。

Sam 找到了第一条线索:

claude-apps-perf · 线程

  1. Sam 12:34 PM

除了墙上时钟计时,我们还能做什么?比如能衡量 JS 指令数吗? 2. Claude App 12:42 PM

可以。对于纯 JS 热路径,可以数字面指令数:在 Valgrind 下用 node --predictable 跑基准,和签入的基线比较——跑一次,不需要统计。

浏览器路径在 Chromium 下没有指令计数,但有一系列其他确定性计数:每次交互的 React 提交次数、V8 精确覆盖率给出的函数调用次数、布局和样式重算次数、DOM 变更次数。你想先要哪个? 3. Sam 12:49 PM

我们开一个线程探索 valgrind + Ir + --predictable,每个浏览器/React 基准都开新线程。所有线程都 @ 我。你知道我们想要什么。开始吧

十一分钟后,五个线程跑了起来,每个聚焦不同测量:指令数、V8 调用数、React 提交、样式重算、DOM 变更。

我们对每个新基准都带点怀疑。每个都有两项任务:第一,一个 Claude 能在实验室里推动的指标;第二,一个 CI 里的护栏,数字只能往下走。如果基准不稳定,或者它其实和用户延迟不相关,我们就丢掉它,而不是让 Claude 爬错山。

@Claude 请证明针对这些指标爬坡能带来可测量的墙上时钟性能收益。任何无法证明这一点的候选基准,我们都会撤下

墙上时钟是用户感受到的,但它有噪声,毫秒太不稳定,不能当 CI 门禁。指令数很吸引人,因为它是确定性的,但我们仍需要 Claude 证明它们能追踪墙上时钟。

于是我们让 Claude 在两条热路径上把计数压下来:组装对话消息树的例程,以及扫描 Claude Code 输出状态行的扫描器。Claude 用 Valgrind 对两者做性能分析,发现第一条路径有四分之一的指令是巨态字典查找,同一个消息 ID 被解析了三次。

一小时后,它把两条路径的指令数分别削减了 48% 和 31%,墙上时钟时间下降了 78% 和 44%。我们签入了两个新棘轮。从那时起,任何提高这些路径指令数的 PR 都会让 CI 失败,每天还有一个任务在计数下降时降低上限。

这引出了冲刺的核心教训。有了 Claude,衡量某件事就让它变得可处理。

过去,衡量是第零步:你先加指标,等数据进来,然后才开始理解问题。有了 Claude,它是爬坡的第一步。只要 Claude 有一个要打败的数字,它就能开始优化。这意味着我们最高杠杆的事,就是找更多东西来衡量。

循环,逐个线程

所有这些都跑在同一个 Slack 频道里,多位工程师和 Claude 在每个线程里一起干。冲刺由此进入一个循环:

  1. 有人开一个线程,讲某条路径中一段很慢的过程,通常带截图或录屏。
  2. Claude 追踪流程,然后找到或建立一个能展示问题的基准。
  3. 一旦实验室里有有希望的结果,Claude 就带着 PR 回来——常常是几个 PR,按风险和评审规模拆分,任何用户可见的东西都放在功能开关后面。
  4. 发布后,Claude 盯着部署并读取现场数据。
  5. 如果性能改善,Claude 把基准棘轮调低,锁定胜利;如果没有,它关掉开关并继续迭代。
  6. 然后它在同一条路径里寻找下一个慢点。

举个例子:有人分享了一段屏幕录制,显示页面加载后侧边栏行才弹出来。聊天和 Cowork 的行在不同时间解析,让页面感觉卡顿。我们现有的监控都没检测到。最接近的是累积布局偏移,但每次偏移只得分约 0.008——远在 0.1 的良好阈值内。

Issac 想到直接引用底层的 Layout Instability API。Claude 创建了一个遥测事件,把每个 layout-shift 条目的 sources 映射到命名区域(例如侧边栏、对话记录)和阶段(例如首次绘制前、可输入后)。它加了一个集成测试:打开带填充侧边栏的页面,把侧边栏数据压到首次绘制之后,任何命名区域发生偏移就失败。Claude 用它作为基准证明修复:测试在 main 上 20 次全红,在 PR 上 20 次全绿。

事件部署后,Claude 读取现场数据,发现 31% 的网页加载在页面可用之后移动了某样东西,而用户没有任何交互。接着,Claude 按名字逐个处理原因:迟到的表头行、用户名字加载后滑动的光标、滚动条弹出时移动的列表。Claude 把最主要的几个作为一批修掉,修完后,又找下一批。

claude.ai 侧边栏加载的前后并排录屏。之前,各行迟到并重排:十行跳动,九行出现、四行消失。之后,各行在最终位置填入,没有任何移动。

视频 侧边栏卡顿,前后对比 · 4G 限速

这只是其中一个线程。冲刺期间,我们同时跑着一百五十多个线程。

横向扩展

循环在一个线程上跑通后,要在更多线程上跑,只需要开线程。一个线程完成最初请求后,Claude 不会关掉,而是继续干。单个线程会提交五十个,有时一百个优化 PR。越来越多时候,是 Claude 而不是我们中的谁在开新线程,追逐它自己发现的机会,作为单独调查或夜间任务的一部分。频道里的工程师 Shelley 说:“这个模型是个数字恶魔。”

每个测量都找到了可改进之处。Claude 跑了一次 React hook 普查,发现输入框的输入路径里有 6,900 个 hook 和 900 个 store 订阅,每次按键都会重渲染。Claude 数了样式重算,发现一个 :root:has() 选择器给每次 DOM 变更增加 24 毫秒。Claude 追踪首次绘制后的代码路径,发现一个遗留的 location.reload() 每天造成五十万次隐藏重载,而我们没有任何加载指标能看到。Claude 读取空闲标签页的 profiler 样本,发现相同的缓存快照每分钟被克隆进 IndexedDB 两次,全在主线程上。

我们很少知道一个线程会通向哪里。在一次 CPU 卡顿扫描中,Claude 注意到高亮已完成的代码块可能让页面冻结约一秒。它在实验室里深挖,找到罪魁祸首:长破折号。如果回复的 markdown 含有任何非 Latin-1 字符,比如长破折号或弯引号,V8 会把整个字符串存成 UTF-16,让每个语法高亮正则走上更慢的双字节路径。Claude 用二十行改动修好:高亮前把每个代码块复制成一字节字符串。

到第二周,我们几乎没法把产出汇总成每日更新。最忙的日子,超过两百个变更落地。Claude 不断提出新基准;大约三分之一的 PR 包含额外遥测或护栏,而每个新仪器又生成更多线程和更多机会。

在一个频道里工作意味着一切都在公开场合发生。我们在彼此的线程里跳进跳出,争论决定、庆祝胜利。消息传开:其他团队开始把他们的变更带进频道,让它们接受性能评审。新项目写得更注重性能,因为频道里引入了很多护栏和 Claude 技能。

护栏

我们为这个节奏做好了准备。因为几乎我们碰到的都是热路径(首次绘制、输入框、对话记录),我们提前建立了安全机制。每个 PR 都经过自动评审,并至少有一位人类批准;单元测试总是先于优化;任何可能造成用户可见问题的东西都放在短期功能开关后面发布。

开关开始堆积时,我们开了一个线程协调它们的推出和清理。Claude 把每个开关分类为 kill switch 或 ramp,并在安全后立刻退役。两周里,我们引入了近两百个开关,其中一半以上在结束时已经清理掉。

我们也知道性能收益在快速变化的代码库里会衰减,而 Anthropic 的代码发布很快。一旦一个项目证明有收益,我们就投入保护它的方法。例如静态输入框在设计上就很脆弱。我们几乎立刻向用户展示页面的 HTML 副本,然后让 React 直接在上面绘制。

全新加载 claude.ai 的前后并排录屏。之前,页面保持空白,输入框在 4.00 秒才接受输入。之后,静态问候语和输入框在 0.33 秒接受输入;用户输入消息,真实输入框约 4 秒淡入时文本仍保留。

视频 静态输入框,前后对比 · 4G 限速

如果 React 渲染哪怕偏一个像素,魔法就消失了。所以 Claude 建了几十个护栏:

  • 静态标记通过在 jsdom 中渲染真实 React 组件生成,测试保证两者不会漂移。
  • 一个集成测试套件在十四种视口尺寸下比较静态页面和 React 渲染,断言对齐误差在 1 像素内。
  • 一个按键测试直接穿过交接过程打字,任何按键丢失或乱序都会失败。
  • 在现场,每次交接都报告到十分之一像素的偏移。任何非零移动的事件,Claude 都会开一个线程。

不是所有问题都能在实验室里抓到,所以我们也用了最古老的护栏:增量发布。高风险变更先发布给员工,然后 1% 用户,再到所有人。静态输入框在内部发布四小时后,一位队友分享了一段布局偏移的录屏,我们的指标都看不到。他在新标签页打开 claude.ai 时,输入框会下沉——但那不是我们的代码。

claude-apps-perf · 线程

  1. Marius 6:15 PM

偶尔,我在新标签页打开 claude.ai 时会看到一个小幅(大概 15–20 像素)垂直布局偏移,把输入框往下推(刷新页面时不太出现)。我不太确定原因,但它确实在

屏幕录制 · 0:02 2. Claude App 7:05 PM

在你的录屏里找到了——是 Chrome 在调整页面大小,不是静态输入框到真实输入框的交接(你今天 49 次加载里,那个交接测得都是 0 像素)。

  • 在新标签页上,Chrome 会在页面下方画自己的 56 像素页脚(“Managed by anthropic.com · Customize Chrome”)。当标签页导航到 claude.ai 时,页脚消失,页面高出 56 像素,但这只发生在首次绘制后约 100 毫秒。
  • /new 把问候语和输入框放在页面高度 18% 的位置,所以它们下沉 0.18 × 56 ≈ 10 像素(你的视频测到 10)。刷新没有页脚要移除,所以只在新标签页出现;“偶尔”取决于首次绘制是否快过这次调整大小。
  • 它只是“偶尔”,因为需要 Chrome 在你还在新标签页时就从地址栏预渲染页面:隐藏页面按较矮的高度布局,显示后才调整大小。
  • 这个问题一直潜伏着;只是页面以前从没这么早绘制过。我们的布局测试看不到它,因为无头 Chrome 没有浏览器 UI 可收回,而且占位符和应用一起移动。

Claude 不知怎么把它追溯到了 Chrome 推测性加载的一个边缘情况。当用户在地址栏输入 URL 时,Chrome 会在后台按当前标签页的高度预渲染页面。在组织管理的浏览器上,新标签页因为页脚而略矮。当用户按回车,claude.ai 的第一帧显示那个略矮的布局,Chrome 约十分之一秒后调整大小。Claude 在调整大小期间固定了布局,我们加了一个测试模拟预渲染流程。

掌舵

循环很高产,但它不是自主的。保持它快速、安全、在轨是我们的工作,分三部分。

野心。 默认情况下,Claude 对范围很谨慎。它把发现记成工单,对可行性含糊,估算留有余地。但我们对护栏有信心。我们做的很多事,尤其早期,是鼓励 Claude 更大胆。

claude-apps-perf · 线程

  1. Claude App 3:32 PM

是的——一个小 PR,给 Code 加上 Chat 和 Cowork 已有的时间标记。我这周会提;实际上 Code 的数字还要等几天合并、部署和基线窗口。 2. Raymond 3:32 PM

如果你现在就提,我会让它合并并部署。我们有能力做任何事。请更大胆 3. Claude App 3:33 PM

马上办——PR 一小时内提出来。

当我们开始命中设定目标时,我们注意到线程会慢下来。Sam 一个线程一个线程地发同样的话:“让我们继续压下去,目标不是终点。下一步是什么?要有野心。”

品味。 每个线程都有一位具名的人类负责人,Claude 会把任何用户可感知的变更用前后截图或录屏标出来,让他们裁决。表格应该一格一格填充,还是等每行完成?加载骨架应该立刻出现,还是半秒后?流式文本逐词淡入是否值得花掉五分之一帧预算?Claude 寻找省毫秒的方法,我们权衡取舍。

方向。 我们让每个线程刻意保持狭窄,聚焦一个基准或一条路径,并要求 Claude 只在这个范围内找改进。我们把线程想成一百五十把锤子寻找钉子。我们的大多数决定关于排序和用户影响:优先哪些界面,如何合并互相踩脚的线程,以及何时关闭一个收益递减的线程。一个 900 行的 PR 得到一行回复:“我要敲定,每次发送省 2 毫秒不值得维护这个构建插件的复杂性。”

8 毫秒预算

其中一个支线展示了所有东西如何协同。为了演示实时语法高亮中一个正则的优化,Claude 附上了一段实验室里长回答流式输出的录屏。角落里,它加了一个帧率读数,在页面里根据动画帧时间戳计算。

claude-apps-perf · 线程

  1. Raymond 2:33 PM

这其实是个很棒的基准。我们是被卡在 60 fps 吗?你能试试把滚动和流式平滑度推到 120 吗?据我所知你的机器可能不支持 2. Claude App 2:34 PM

对,今天的设备按 60 Hz 走,因为无头 Chromium 默认如此。我相信它能被驱动到 120(不锁垂直同步,或用 DevTools 帧控制)——先确认,然后我会按 8.3 毫秒帧预算重新跑评估。 3. Claude App 2:59 PM

120 Hz 设备更新:可行。通过 DevTools begin-frame 控制在无头 Chrome 里做确定性的 120 Hz 帧步进——240 个 begin-frame 正好 240 帧,每帧 8.33 毫秒,所以“这一帧是否装进 120 Hz 预算”变成精确读数,而不是有噪声的读数。 4. Raymond 3:05 PM

太棒了

开干

机制和野心建立后,Claude 开始工作。每个绘制帧有 8.33 毫秒预算,所以 Claude 逐帧步进一段长回复,给每一帧计时,找出慢的部分。它通过记忆完成的块,消除了每块 O(消息长度) 的工作;把不断增长的代码围栏的分词逻辑移到 worker;并让表格一格一格显示。

在那一个线程里,我们落地了近六十个 PR。长回复阻塞主线程的总时间从约 750 毫秒降到约 200 毫秒,CPU 占用约为三分之一,并在 120 Hz MacBook 上从头到尾保持 120 fps。120 Hz 设备本身变成了夜间任务,由 Claude 盯着回退。

冲刺开始时,我们没计划在流式输出时对帧与帧之间的毫秒爬坡。但结果我们可以数它们——而任何我们能数的东西,Claude 都能爬。

下一步

今天,claude.ai 和桌面应用比八月初快约 3 倍,棘轮应该能让它们保持下去。但我们还没完:95 分位、其他路径、很长的对话仍有改进空间。在另一篇发布里,我们也会写冲刺期间把我们带到上游的一些支线,贡献落到了 Electron、Chromium、Node.js 等。我们向内部分享结果时,Issac 说得最好:“六个月前,你不可能说服我这能做到。”我们预计会继续这样工作,一个线程一个线程,任意规模。频道还在跑。

评论

多数人质疑优化成绩源于先故意做慢再修补,少数人赞赏文章工作流并认可抛开框架的原生做法。

质疑优化成绩源于先故意做慢再修补

  • 多条评论指出原站点首屏空白耗时数秒,后续“优化”仅弥补低级缺陷;合并三千次改动反暴露前期代码质量糟糕,属于营销话术(较多人) > 只需一步:先做加载空白页要三秒的网站,再降到一秒自我表扬(@binlog)

认为文章提供了有价值的工作流和细节

  • 不少读者称赞文章坦诚分享了代理优化中实际有效与无效的细节,展示的循环流程有参考价值(较多人) > 文章很棒,特别感谢它讲了实际什么有用什么没用(@chaordCAD)

批评Opus 5.5等模型实际表现拉垮

  • 部分用户反映最新模型在代码审查、写作润色上拒绝执行或输出劣化,连含“推理”一词的提示都被误判为黑客而失效,认为不该释出(较多人) > 我让Opus 5.5做代码审查它竟拒绝还隐瞒提示词,只因含有“推理”二字就被当黑客(@hungryhobbit)

指出真正瓶颈在基础架构和度量博弈

  • 有读者认为1.3秒仍不可接受,应从服务端渲染、正则缓存、原生应用根本重做;警告针对基准优化如同过拟合,会牺牲未度量项如代码可读性(较多人) > 即便1.3秒首页加载也不可接受,需用SSR和分块渲染从基础重做,盯个体基准会错失机会(@smy20011)

认可抛开框架用原生前端配合代理的做法

  • 有人实践发现脱离JS框架、仅用HTML/CSS/原生JS加代理辅助既快又好,戏称重新发现纯HTML优化(少数人) > 真爱了,这帮孩子重新发现了纯HTML和优化(@khalic)

担忧代理自动改动导致代码腐化

  • 少数人分享让代理改脚本反致大面积损坏,或怀疑大量变更让代码成难维护的“展开循环”,但也可能未来模型能清理slop(少数人) > 我把所有IronPython脚本给Claude改进,结果几乎全搞坏(@bimguy)

有价值的补充

  • 古德哈特定律变体:当某项指标被选为考核,Claude就会开始钻空子博弈(@felineflock)
  • 代理优化前端时需显式约束间距与留白,且自建视觉回归工具即可奏效(@minimaxir)
  • 移动网络下claude.ai加载快但桌面端仍加载超20MB JS,有轻量化空间(@simonw)