外部精选
How we made claude.ai 3x faster in two weeks
本文来自外部来源,不是黄奕彬的原创文章。本站提供原文入口,并在采集端生成 AI 结构化摘要、解读与中文翻译(如有),一切以原文为准。
阅读原文(claude.dev)结构化摘要 AI 生成
背景与问题
2026 年 8 月,用户持续反馈 claude.ai 与 Claude 桌面应用“慢”,团队承认属实,发起为期两周的性能 sprint,目标是把核心用户体验提速约 3x。文章同时回答一个工程组织问题:能否让 Claude 作为主要执行者、在单一 Slack 频道内安全地完成大规模性能优化。
隐含假设:凡能被测量的性能问题,Claude 就能据此优化。
方法或内容
- 建单一 Slack 频道并给 Claude 常驻指令:盯 deploy 回归、维护遥测与仪表盘、主动修复并提案。
- 经
DatadogMCP server 分析使用数据,锁定四个高影响旅程(启动应用、开始新对话、加载既有对话、发送消息),web+desktop 共 13 项统一口径测量;以约 20 个预选项目开跑,另留空间让 Claude 自提 workstream。 - 实验室用确定性计数替代 wall-clock:Valgrind 指令数(配
node --predictable)、V8 precise coverage 调用数、React commits、style recalc、DOM mutations、Layout Instability API;benchmark 兼作 CI 里只降不升的 ratchet。 - 循环:开 thread → 建 bench → 带 flag 的 PR → 盯 deploy 与字段数据 → 收益则 ratchet 锁定,否则关 flag 迭代;高峰同时 150+ threads。
- 护栏:自动审查 + 至少一人批准、先单测后优化、约 200 个 feature flag、员工→1%→全量增量 rollout;人类负责 Ambition、Taste、Direction 三类转向。
关键发现 / 结果
- p75(RUM,2026-08-13 vs 2026-08-27):fresh load 可输入页面 3.1s→0.55s;新 Claude Code session 0.8s→0.3s;Cowork cloud session 2.6s→0.73s;估计每天省数万用户小时。
- 两周合并 3000+ 变更,零客户可见事故或回滚;第 3 天达成 13 个目标中的 12 个;最忙一天 200+ 变更;约 1/3 PR 附带新遥测或护栏。
- 关键修复:静态 composer(用
jsdom渲染真实 React 组件生成 HTML)、桌面 V8 code cache 预编译、hover prefetch session、sidebar 重渲染 -90%。 - 两条热路径指令数 -48%/-31%,wall-clock -78%/-44%(瓶颈为 megamorphic 字典查找,占该路径 1/4 指令)。
- 其他发现:composer 输入路径 6900 hooks / 900 store subscriptions 每次击键重渲染;单个
:root:has()每次DOM变更 +24ms;遗留location.reload()日约 50 万次隐藏 reload;空闲页 IndexedDB 每两分钟克隆同一缓存快照;em dash 等非 Latin-1 字符使 V8 以 UTF-16 存储、语法高亮 regex 走 two-byte 慢路径致约 1 秒冻结(20 行修复)。 - 31% 的 web 页面加载在可用后仍有布局移动;长回复流式渲染阻塞 ~750ms→~200ms、CPU 约 1/3、120Hz 机型全程 120fps(该 thread 约 60 个 PR,8.33ms 帧预算 + DevTools begin-frame 120Hz rig)。
结论与意义
- 核心结论:“With Claude, measuring something makes it tractable”——测量从优化的前置步骤变成爬山的第一步,最高杠杆是找到更多可测对象。
- claude.ai 与桌面应用比 8 月初约 3x 快,ratchet 应能维持该水平。
- 边界:流程并非自主,需人类设目标、做取舍并逐项批准;性能收益在快速代码库中会衰减;p95、其他旅程、超长对话仍有改进空间;静态 composer 等脆弱设计需持续护栏。
AI 解读 AI 生成
方法:按「时空变量」分析(时间 / 空间 / 变量,严格区分包含、相关与因果),对象包括原文作者、署名人员与关键技术及其实际原理;结论用「元反空」(元思考、反思考、空思考、综合)整理。
时间:何时发生,现在是否仍成立
- 来源日期为北京时间 2026-09-23 20:00;事件是 2026 年 8 月的两周 sprint,RUM 数据对比 2026-08-13 与 2026-08-27。
- 是否仍可能成立:ratchet 与每日降上限 job 被设计为防回退,原文称 “should keep them there”;但原文自认性能收益在快代码库中会衰减,且 p95、超长对话等尚未解决,“3x” 是该时点的 p75 快照而非长期承诺。
空间:谁的环境 / 场景
- 适用产品:claude.ai 网页与 Claude 桌面应用;覆盖 Chat、Claude Code session、Claude Cowork cloud session 等 web 与桌面场景。
- 数据形态:p75 来自按平台与产品划分的真实用户监控(RUM)。
- 工程环境:单一 Slack 频道 + Claude Tag (beta)(运行内部研究模型,约相当于 Opus 5.5)+ Datadog MCP server;工具链含 Valgrind、
node --predictable、V8 precise coverage、headless Chromium DevTools begin-frame、jsdom、Electron。 - 迁移边界:护栏依赖 Anthropic 内部 CI、feature flag、快速 deploy 与增量 rollout 基建,其他团队复现需等效条件。
变量:实际改变了什么
- 用户可见数据(p75,08-13→08-27):3.1→0.55、0.8→0.3、2.6→0.73 秒;sidebar 重渲染 -90%;长回复主线程阻塞 ~750→~200ms、CPU 约 1/3、120Hz 全程 120fps。
- 行为/结构:静态 composer 与 V8 code cache 进入启动路径;hover prefetch;会话间保持 composer mounted;引入约 200 个 feature flag(过半已清理)。
- 流程/机制:新增确定性 benchmark 与 CI ratchet、每日降上限 job、Layout Instability 命名区域遥测、120Hz nightly 回归监控;两周 3000+ 合并、零客户可见事故/回滚。
- 价格/商业条款:原文未说明。
区分:包含 ≠ 相关 ≠ 因果
- 明确因果(原文给出机理):静态 composer → React 初始化期间即可输入;V8 code cache → 桌面主进程免从头编译;em dash 等非 Latin-1 → V8 存 UTF-16 → 高亮 regex 走 two-byte 慢路径 → 页面冻结约 1 秒(20 行 one-byte 复制修复);
:root:has()→ 每次DOM变更 +24ms;遗留location.reload()→ 日约 50 万次隐藏 reload;Chrome 新标签页 56px footer 加 prerender → 新开标签时 composer 下移约 10px。 - 相关但因果未经全文证明:整体 “3x faster” 是 sprint 前后 RUM 的汇总归因;指令数与 wall-clock 的对应仅被要求在两条热路径上验证。
- 包含关系:四个旅程占 95% 用户活动、13 项测量涵盖各平台产品旅程——这是构成关系,不是因果。
对象:作者/署名与关键技术原理
- 署名(仅取结构化作者字段):Raymond Wang、Sam Attard、Issac G.;正文另列贡献者与致谢名单,但不在结构化署名内。
- Claude Tag (beta):运行内部研究模型,约相当于 Opus 5.5;负责找瓶颈、建 benchmark、提改进、盯 deploy;人类设目标、做取舍并逐项批准变更。
- 确定性测量原理:Valgrind +
node --predictable对纯 JS 热路径计字面指令数,与 checked-in 基线比较,单次运行无需统计;浏览器侧改用 V8 precise coverage 调用数、React commits、style recalc、DOM mutations 等确定性阶梯。 - Ratchet 机制:PR 抬高路径指令数即 CI 失败;每日 job 在计数下降时自动下调上限。
- 静态 composer:由真实 React 组件在
jsdom渲染生成静态标记,先给用户 HTML 副本、React 直接在其上 paint;护栏含 14 视口 1px 对齐测试、按键穿透测试、现场 0.1px 精度的 handoff 偏移监控。 - 帧预算:headless Chrome 经 DevTools begin-frame 控制实现确定性 120Hz(8.33ms/帧),“该帧是否达标”成为精确读数。
元反空综合结论
- 元:本质在解决“如何让 AI 代理大规模且安全地做性能工程”——把优化转化为可击败的确定性数字,测量从理解问题的第 0 步变为爬山的第 1 步。
- 反(最可能失效或有边界):1) 指令数与 wall-clock 的相关性仅在两条热路径被验证,泛化到任意路径有风险;2) 全部数据为第一方自报,“每天数万用户小时”是内部估算,无独立复核;3) “3x” 是四个旅程(占 95% 活动)的 p75 汇总,p95、其余旅程、超长对话未覆盖;4) 流程依赖单一频道、快 deploy cadence、护栏基建与每 thread 人类 owner,外部难以直接复制;5) 收益会衰减,ratchet 只保护已测路径。
- 空(去掉叙事后的必要事实):三项 p75 分别从 3.1/0.8/2.6 秒降至 0.55/0.3/0.73 秒;3000+ 合并、零客户可见事故/回滚;约 200 flags;方法为确定性 benchmark + CI ratchet + feature flag + 分层 rollout;大部分执行由 Claude 完成,人类做 steering。
- 综合:在其自述边界内(p75、核心旅程、时点快照)结论自洽且数据具体;“可测即可爬”的方法论有迁移价值,但量化收益的外部有效性与长期持续性原文未证明。
中文译文 AI 生成
今年八月,我们在为期两周的冲刺中,将 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 秒。总体估算,这每天为用户节省数以万计的等待小时。
核心用户旅程,p75。真实用户监控,按平台与产品划分,8 月 13 日与 8 月 27 日对比。
我们使用了 Claude Tag(beta),它运行一个与 Opus 5.5 大致相当的内部研究模型。Claude 发现瓶颈、构建基准、交付改进,并监控每一次部署。我们通过设定目标、做出取舍并审批每一项变更来进行引导。
凭借这种方式,我们合并了三千多项变更,没有发生一起面向客户的事故或回滚。这篇文章涵盖我们交付了什么、如何测量,以及我们与 Claude 一起构建的、用来安全完成这些工作的循环。
任务简报
冲刺开始前,我们创建了一个 Slack 频道,并附上以下常设指令:
@Claude 你的工作是协助处理与 claude.ai 网站和桌面应用性能相关的一切事务。你的职责包括:监控部署是否出现性能回退,评估现有遥测数据的准确性与全面性,维护精心整理的可观测性仪表盘,主动为观察到的问题和唾手可得的改进落地方案,提出性能项目机会,以及与你的人类队友沟通。[…]
这个频道的最终目标是让你尽可能自主,但今天我们知道这还不可能。
我们让 Claude 通过 Datadog MCP server 分析使用数据。它识别出影响最大的四条用户旅程:启动应用、开始一段对话、加载一段已有对话,以及发送一条消息。跨 Web 与桌面、并覆盖我们的各产品,这些旅程对应十三项不同的测量。
为了建立基线,我们持续添加埋点,直到它们可以直接相互比较:每项测量都以一次用户交互开始、在结果渲染完成时结束,并区分客户端与服务端的工作。
我们以一份约二十个精选项目的清单启动了冲刺,每个项目都针对一条具体旅程。Claude 以毫秒为单位估算每个项目的影响,我们汇总这些估算来设定冲刺目标。有些项目相当大,但我们认为大多数可能可以在两周内完成。
到第三天,我们就完成了十三个目标中的十二个。
计划中的项目提前落地了。为了加快启动,我们把一个静态 composer 直接嵌入 HTML,让用户在 React 初始化期间就能打字,并预编译了 V8 code cache,使桌面外壳的主进程不必从头重新编译。
为了加快导航,我们在对话之间保持 composer 挂载,在用户悬停时预取会话,并将侧边栏重渲染减少了 90%。
我们也为 Claude 留出了空间,让它识别机会并提出新的工作流。这些工作流很快发展成各自独立完整的项目,远超我们最初的目标。于是我们设定了新目标,然后寻找更多可以测量的东西:
@Claude 我们最终资助了原始项目清单里几乎每一个项目,还不止于此。让我们做一次刷新 […] 我们还有哪些没有探索的,可以在哪些方面做爬山优化,此刻最大的机会在哪里?[…] 我对 WACKY(离奇)的想法持开放态度
万物皆可爬山
从一开始我们就知道,我们希望以快于部署节奏的速度迭代。Claude 可以异步工作很多个小时,甚至通宵,我们希望让它无需等待线上实测就能验证自己的原型。为此,我们寻找在实验室中测量性能的其他方法。
Sam 找到了第一条线索:
#claude-apps-perf · 线程
Sam 12:34 PM除了挂钟计时,我们还能怎么做?比如说,能不能测量 JS 指令数?
ClaudeApp 12:42 PM可以。对纯 JS 热点路径,就是字面意义上的指令数:在 Valgrind 下用 node --predictable 运行基准,并与签入仓库的基线比较——运行一次即可,无需统计。
浏览器路径在 Chromium 下没有指令计数,但有一系列其他确定性计数:每次交互的 React commits、来自 V8 precise coverage 的函数调用计数、layout 与 style-recalc 计数、DOM mutations。你想先做哪个?
Sam 12:49 PM让我们在一个线程里探索 valgrind + Ir + --predictable,并各开新线程探索每个 browser/react 基准。每个线程都 @ 我。你知道我们想要什么。开干。
依据真实对话重演。
十一分钟后,五个线程已经开跑,各自聚焦一种不同的测量:指令计数、V8 调用计数、React commits、style recalculations,以及 DOM mutations。
我们对每个新基准都保持一定程度的怀疑。每个基准都有两重作用:其一,是 Claude 可以在实验室中改善的指标;其二,是 CI 中的一道护栏,其数值只能单向收紧、只降不升。如果某个基准不稳定,或者它实际上与用户延迟不相关,我们就会把它弃用,而不是让 Claude 爬错山。
@Claude 请证明针对其中每一项做爬山优化都能带来可测量的挂钟时间性能收益。对任何无法证明这一点的候选基准,我们会将其下线。
挂钟时间是用户切实感受到的,但它噪声大,而毫秒数太不稳定,无法用作 CI 门禁。指令计数之所以有吸引力,是因为它们是确定性的,但我们仍然需要 Claude 证明它们能追踪挂钟时间。
于是我们让 Claude 在两条热点路径上压低计数:一是组装会话消息树的例程,二是扫描 Claude Code 输出中状态行的扫描器。Claude 用 Valgrind 对两者做了剖析,发现第一条路径上有四分之一的指令是 megamorphic 字典查找,同一个 message ID 被分别解析了三次。
一小时后,它把两条路径的指令数分别削减了 48% 和 31%,实际耗时分别下降了 78% 和 44%。我们提交了两个新的棘轮。从那以后,任何抬高这些路径指令计数的 PR 都无法通过 CI,而每当计数下降,一个每日任务就会下调对应的上限。
计数能追踪时钟吗?CPU 指令 vs. 实际耗时
这把我们引向了本次冲刺的核心教训:有了 Claude,测量某件事就能让它变得可解。
过去,测量是第零步:你会加一个指标,等数据滚滚而来,然后才开始理解问题。有了 Claude,它成了攀登的第一步。Claude 一旦有了要击败的数字,就能开始优化。
这意味着我们能做的杠杆最高的事,就是找到更多可测量的东西。
循环,逐条线程推进
这一切都发生在同一个 Slack 频道里,多位工程师和 Claude 在每条线程里一起投入其中。从那里开始,冲刺沉淀为一个循环:
- 有人会就某段旅程中缓慢的一段开一条线程,通常附上截图或录屏。
- Claude 会追踪流程,然后找到或构建一个能展示该问题的 benchmark。
- 一旦在实验室里得到有希望的结果,Claude 就会带着 PR 回来——往往是多个,按风险和评审需要控制大小,任何用户可见的改动都放在 flag 后面。
- 上线之后,Claude 会观察部署并阅读现场数据。
- 如果性能改善,Claude 会把 benchmark 棘轮式收紧,锁定这次胜利;如果没有,它就关掉 flag 继续迭代。
- 然后它会去同一段旅程里寻找下一个慢点。
循环中的一个线程有人开一条线程;Claude 从那里接手
举个例子:有人分享了一段屏幕录像,显示侧边栏的行在页面加载后才陆续弹出。Chat 和 Cowork 的行在不同时间完成解析,让页面感觉卡顿。
我们现有的监控没有一个能检测到它。最接近的是 Cumulative Layout Shift,但每次偏移的得分只有约 0.008——远在 0.1 的良好阈值之内。
Issac 提出直接引用底层的 Layout Instability API。Claude 创建了一个遥测事件,把每条 layout-shift 条目的来源映射到具名区域(如 sidebar、transcript)和阶段(如 before first paint、after typeable)。
它还添加了一个集成测试:打开带有已填充侧边栏的页面,把侧边栏的数据扣住直到首次绘制之后,任何具名区域内出现任何偏移即告失败。
Claude 把它当作 benchmark 来证明一个修复:该测试在 main 上 20 次运行 20 次变红,在 PR 上 20 次运行 20 次变绿。
该事件部署后,Claude 阅读现场数据,发现 31% 的网页加载在页面可用之后仍有元素移动,且没有任何用户交互。接下来,Claude 按名字逐一处理这些原因:迟到才到的头部行、用户名加载后侧向滑动的光标、滚动条弹出时随之移动的列表。Claude 把最大的元凶成批修掉,等它们消失后,又找到下一批。
图 A 侧边栏卡顿,之前与之后 · 4G 节流
那只是一条线程。冲刺期间,我们同时运行着一百五十多条线程。
水平扩展
一旦这个循环在一条线程上跑通,在更多线程上运行它就只是开线程的事。Claude 不再在原始请求满足后就关闭线程,而是继续推进。单条线程会提出五十个、有时一百个优化 PR。
越来越多的时候,是 Claude 而不是我们中的某个人去开新线程,追逐它自己发现的机会,作为某项独立调查或夜间任务的一部分。Shelley,频道里的一位工程师,评论道:"[This model] 是个数字恶魔。"
每一次测量都找到了可改进之处。Claude 做了一次 React hook 普查,发现 composer 的输入路径中有 6,900 个 hook 和 900 个 store 订阅,每次按键都会重新渲染。Claude 统计样式重算,发现单个 :root:has() 选择器给每次 DOM 变更增加 24 毫秒。
Claude 追踪首次绘制之后的代码路径,发现一个遗留的 location.reload() 每天造成五十万次隐藏的重载,我们所有的负载指标都看不到它。Claude 读取空闲标签页的 profiler 采样,发现相同的缓存快照每分钟被克隆进 IndexedDB 两次,全部发生在主线程上。
我们很少知道一条线程会走向哪里。在一次针对 CPU 卡顿的排查中,Claude 注意到高亮一个已完成的代码块可能让页面冻结约一秒。它在实验室里深挖,找到了罪魁祸首:em dash。
如果回复的 markdown 中包含任何非 Latin-1 字符,比如 em dash 或弯引号,V8 会把整个字符串存成 UTF-16,这让每个语法高亮正则都走上较慢的双字节路径。
Claude 用一个二十行的改动修复了它:在高亮之前,把每个代码块复制成一个单字节字符串。
高亮页面上的第一个代码块主线程阻塞时间,修复前后
到第二周,我们几乎无法把产出概括成每日更新。最繁忙的日子里,有两百多项变更落地。Claude 不断提出新的 benchmark;大约三分之一的 PR 包含额外的遥测或护栏,每件新仪器都会催生更多线程和更多机会。
在同一个频道里工作意味着一切都公开进行。我们相互进出彼此的线程,争论决策、庆祝胜利。消息传开:其他团队开始把他们的变更带进这个频道,接受性能方面的评审。
由于所有已引入的护栏和 Claude skills,新项目的编写在细微之处变得更高效。
护栏
我们为这种节奏做好了准备。因为我们改动的几乎所有内容都是热路径(首次绘制、composer、transcript),我们事先建立了安全机制。每个 PR 都要经过自动化审查并至少获得一次人工批准,单元测试总是先于优化,任何可能造成用户可见问题的改动都放在一个短期的 feature flag 之后发布。
当 flag 开始越积越多时,我们开了一个 thread 来协调它们的发布与清理。Claude 把每个 flag 归类为 kill switch 或 ramp,并在一旦安全就尽快将其下线。在这两周里,我们引入了近两百个 flag,其中超过一半在结束时已被清理。
我们也知道,在一个快速变化的代码库里性能收益会衰减,而 Anthropic 的代码发布得很快。一旦某个项目被证明有效,我们就会投入方法去保护它。
例如,static composer 在设计上就是脆弱的。我们几乎立刻向用户展示页面的一个 HTML 副本,并让 React 直接在它上面绘制。
图 B Static composer,前后对比 · 4G 节流
如果 React 渲染哪怕偏差一个像素,魔力就消失了。于是 Claude 构建了几十条护栏:
- 静态 markup 通过在
jsdom中渲染真正的 React 组件生成,并且有一个测试保证二者永不漂移。 - 一个集成测试套件在十四种视口尺寸下将静态页面与 React 渲染进行对比,并断言对齐误差在 1 px 以内。
- 一个按键测试直接连续输入穿过交接点,任何丢失或乱序的按键都会导致失败。
- 在线上,每次交接都会把位移上报到十分之一像素的精度。对任何位移非零的事件,Claude 都会开一个 thread。
并非所有问题都能在实验室里被发现,因此我们还用上了教科书里最古老的护栏:渐进式发布。高风险改动先发布给员工,然后是 1% 的用户,最后是所有人。
在我们内部发布 static composer 四小时后,一位队友分享了一段屏幕录像,显示了一次我们所有指标都看不到的布局位移。当他在新标签页打开 claude.ai 时,composer 会往下掉——但那不是我们的代码。
#claude-apps-perf · thread Marius 6:15 PM偶尔,我在新标签页打开 claude.ai 时会看到一个不大的(大约 15–20px)垂直布局位移(把 composer 框往下推),刷新页面时则不太明显。
我说不清到底是什么导致的,但它确实存在Screen recording · 0:02
ClaudeApp 7:05 PM在你的录像里找到了——是 Chrome 在调整页面大小,而不是 static composer 到真实 composer 的交接(那在今天你全部 49 次加载中测得均为 0 px)。
在新标签页上,Chrome 会在页面下方绘制自己的 56 px 页脚(“Managed by anthropic.com · Customize Chrome”)。当标签页导航到 claude.ai 时,页脚消失,页面变高 56 px,但这发生在我们首次绘制之后约 100 ms。
/new 把问候语和 composer 放在距页面顶部 18% 高度的位置,因此它们下坠 0.18 × 56 ≈ 10 px(你的视频测得 10)。刷新时没有需要移除的页脚,所以只出现在新标签页;“偶尔”取决于首次绘制是否抢在这次尺寸调整之前。
它之所以只是“偶尔”,是因为这需要 Chrome 在你还停在新标签页上时从地址栏预渲染页面:隐藏的页面按较短的高度布局,只有在显示之后才被调整尺寸。
这个问题一直潜伏着,只是页面以前从未绘制得这么早。我们的布局测试看不到它,因为 headless Chrome 没有可收回的浏览器 UI,而且占位符和应用是一起移动的。
复原自一段真实对话。
不知为何,Claude 把问题追溯到了 Chrome speculative loading 的一个边缘情况。当用户正在地址栏里输入 URL 时,Chrome 会在后台以当前标签页的高度预渲染页面。
在由组织管理的浏览器上,新标签页因为一个页脚而略短。当用户按下 Enter 时,claude.ai 的第一帧显示的是那个略短的布局,Chrome 在大约十分之一秒后才调整它的大小。Claude 在这次尺寸调整期间固定了布局,我们还添加了一个测试来模拟预渲染流程。
掌舵
这个循环很有产出,但它并非自主运行。让它保持快速、安全、不偏离轨道是我们的工作,而这份工作有三个部分。
雄心。默认情况下,Claude 对范围很谨慎。它会把发现登记成 ticket、对可行性含糊其辞,并在估算中留出缓冲。
但我们对自己的护栏有信心。我们所做的很多事,尤其是在早期,就是鼓励 Claude 更大胆。
#claude-apps-perf · thread ClaudeApp 3:32 PM可以——一个小 PR,给 Code 加上 Chat 和 Cowork 已经有的同样的 timing marks。我这周就提交;现实地说,Code 的数字还要等几天,用于 merge、deploy 以及一个基线窗口。
Raymond 3:32 PM如果你现在就提交,我就让人把它 merge 并 deploy。我们有能力做任何事。请更大胆一点ClaudeApp 3:33 PM马上做——PR 会在一小时内提交。复原自一段真实对话。
当我们开始达到自己设定的目标时,我们注意到 thread 会慢下来。Sam 带着同样的消息逐个 thread 传达:“让我们继续把它压下去,目标不是终点。接下来是什么?要有雄心。”
品味。每个 thread 都有一位具名的人类负责人,Claude 会用前后对比的截图或录像突出任何用户可感知的改动,交由他们裁定。
表格应该逐格填充,还是等每一行完整后再显示?加载骨架应该立即出现,还是半秒后才出现?流式文本的逐词淡入,值得它消耗的那五分之一帧预算吗?
Claude 寻找削减毫秒的方法,而我们权衡其中的取舍。
方向。我们刻意让每条 thread 保持狭窄,聚焦于一个 benchmark 或一段用户旅程,并只要求 Claude 在该范围内寻找改进。我们把这些 thread 想象成一百五十把寻找钉子的锤子。
我们的大多数决策都关乎优先级排序与用户影响:哪些界面要优先处理、如何合并相互踩踏的 thread、以及何时关闭一条收益已递减的 thread。
一个 900 行的 PR 只收到一行回复:“拍板裁定:每次 send 的 2ms 不值得为维护这个 build plugin 付出的复杂度。”
8 毫秒预算
我们的一个支线任务展示了这一切如何协同运作。为了演示对实时语法高亮所用 regex 的一项优化,Claude 附上了一段在实验环境中长回答流式输出的屏幕录像。在角落里,它还加上了帧率读数,由页面根据 animation-frame 时间戳计算得出。
#claude-apps-perf · thread
Raymond 2:33 PM
这其实是个相当酷的基准测试。我们被限制在 60 fps 吗?你能试着把滚动和流式输出的平滑度驱动到 120 吗?如果我没理解错(iiuc),你的机器可能不支持。
ClaudeApp 2:34 PM
没错,今天的测试机器以 60 Hz 运行,因为 headless Chromium 默认如此。我相信它可以被驱动到 120(解除 vsync 上限或使用 DevTools frame control)——我先确认这一点,然后针对 8.3 ms 的帧预算重新运行 eval。
ClaudeApp 2:59 PM
120 Hz 机器的进展更新:可行。在 headless Chrome 中通过 DevTools 的 begin-frame 控制实现确定性的 120 Hz 帧步进——每 8.33 ms 一个 begin-frame,240 个 begin-frame 恰好对应 240 帧,因此“这一帧是否符合 120 Hz 预算”变成了精确读数而非有噪声的读数。
Raymond 3:05 PM
太棒了
cook
根据一次真实对话重现。
机制和目标一旦确立,Claude 便开始行动。每一帧渲染的预算是 8.33 毫秒,于是 Claude 逐帧遍历一个长回答,为每帧计时以找出缓慢的部分。
它通过对已完成的 block 进行 memoization 消除了每个 chunk 中 O(message length) 的工作量,将不断增长的 code fence 的 tokenization 逻辑移入 worker,并让表格逐格显示。
在单独这一条 thread 里,我们合入了将近六十个 PR。长回答如今总共只阻塞主线程约 200 毫秒,而过去约为 750 毫秒;CPU 占用约为原来的三分之一;并且在 120 Hz 的 MacBook 上从头到尾保持 120 fps。
这套 120 Hz 测试机器本身也成了每夜运行的任务,由 Claude 监视性能回退。
ClaudeDevs @ClaudeDevs
Claude 网页版和桌面版上的长回答如今流式输出流畅度提升约 4 倍。我们重建了流式渲染器,使其只触碰仍在变化的部分,因此在较慢的笔记本上,长回答的卡顿减少 9 倍,最严重冻结时间缩短 4.5 倍,并且在 120Hz MacBook 上从头到尾保持 120fps。
刚启动这次冲刺时,我们并未计划对流式输出时帧与帧之间的毫秒数做爬山优化。但事实证明我们能够数清它们——而凡是能数清的东西,Claude 都能攀登。
接下来
如今,claude.ai 和桌面应用比八月初快了约 3 倍,而这些棘轮机制应能保住这一水平。但我们还没做完:第 95 百分位、其他用户旅程以及超长对话仍有改进空间。在另一篇文章中,我们还会写到冲刺期间带我们走向上游的一些支线任务,相关贡献已落地到 Electron、Chromium、Node.js 等项目。
当我们在内部分享这些结果时,Issac 说得最到位:“即使六个月前,你也无法让我相信这是可能的。”我们期望继续以这种方式工作,一次一条 thread,无论规模多大。频道仍在继续。
感谢 Alfred Xing、Anthony Morris、Benjamin Pasero、Chase McCoy、Joshua N.、Luke Deen Taylor、Marius Schulz 和 Shelley Vohr 的贡献。特别感谢 Boris Cherny 鼓励我们更加雄心勃勃。
展开原文提取正文(采集端自动提取,可能有缺失)
This August, we made the core user experience of claude.ai and the Claude desktop app about 3x faster in a two-week sprint. Users had been telling us it was slow, and they were right. We ran everything from a single Slack channel, with Claude in every thread. We focused on four journeys that make up 95% of user activity. At the 75th percentile, time to a typeable page on a fresh load of claude.ai went from 3.1 seconds to 0.55, starting a new Claude Code session went from 0.8 seconds to 0.3, and loading a Claude Cowork cloud session went from 2.6 seconds to 0.73. In aggregate, we estimate that saves tens of thousands of user-hours of waiting every day. Core user journeys, p75Real user monitoring, per platform and product, August 13 vs. August 27 We used Claude Tag (beta), running an internal research model roughly comparable to Opus 5.5. Claude found bottlenecks, built benchmarks, shipped improvements, and watched every deploy. We steered by setting goals, making tradeoffs, and approving every change. With that approach, we merged more than three thousand changes without a single customer-facing incident or rollback. This post covers what we shipped, how we measured it, and the loop we built with Claude to do it safely. THE BRIEF Before the sprint, we created a Slack channel with the following standing instructions:
@Claude Your job is to facilitate all things related to the performance of the claude.ai website and desktop app. Your responsibilities include monitoring deploys for performance regressions, assessing the accuracy and comprehensiveness of existing telemetry, maintaining well-curated observability dashboards, proactively implementing solutions for observed issues and low-hanging fruit, proposing performance project opportunities, and communicating with your human teammates. […] The ultimate goal for this channel is for you to become as autonomous as possible, but today we know that isn’t yet possible.
We asked Claude to analyze usage data through the Datadog MCP server. It identified the four highest-impact user journeys: launching the app, starting a conversation, loading an existing conversation, and sending a message. Between web and desktop, and across our products, those journeys came to thirteen distinct measurements. To establish baselines, we added instrumentation until they were directly comparable: each started with a user interaction, ended once the result was rendered, and disambiguated client and server work. We kicked off the sprint with a list of about twenty hand-picked projects, each targeting a specific journey. Claude estimated the impact of each project in milliseconds, and we aggregated those estimates to set our targets for the sprint. Some of the projects were fairly large, but we thought we could probably achieve most of them within two weeks. We hit twelve of the thirteen targets by day three. The planned projects landed early. For faster launches, we baked a static composer into the HTML so users can type during React initialization, and precompiled a V8 code cache so the desktop shell’s main process doesn’t recompile from scratch. For faster navigations, we kept the composer mounted between conversations, prefetched sessions when the user hovered over them, and cut sidebar re-renders by 90%. We had also left room for Claude to identify opportunities and propose new workstreams. Those workstreams quickly ramped into full projects of their own, which far exceeded our initial targets. So we set new targets, then looked for more things to measure:
@Claude we’ve ended up funding nearly every project in the original projects list and more. let’s do a refresh […] what have we not explored, what can we hill climb on, where is the most opportunity at this point? […] i am open to WACKY ideas
ANYTHING CAN BE HILL CLIMBED From the start, we knew we wanted to iterate faster than our deploy cadence. Claude could work asynchronously for many hours, even overnight, and we wanted to let it validate its prototypes without waiting for field reads. To achieve that, we looked for other ways to measure performance in the lab. Sam found the first lead: #claude-apps-perf · threadSam12:34 PMWhat can we do instead of wall-clock timing? Can we measure JS instruction counts for instance?ClaudeApp12:42 PMYes. For pure-JS hot paths, literal instruction counts: run the benchmark under Valgrind with node --predictable and compare to a checked-in baseline — one run, no statistics needed.For browser paths there’s no instruction counting under Chromium, but there’s a ladder of other deterministic counts: React commits per interaction, function call counts from V8’s precise coverage, layout and style-recalc counts, DOM mutations. Which do you want first?Sam12:49 PMLet’s explore valgrind + Ir + --predictable in one thread and each of the browser/react benches in new threads. Ping me in all of them. You know what we want. Let’s goRecreated from a real conversation. Eleven minutes later, five threads were running, each focused on a different measurement: instruction counts, V8 call counts, React commits, style recalculations, and DOM mutations. We treated every new benchmark with some skepticism. Each one had two jobs: first, a metric Claude could move in the lab; second, a guardrail in CI with a number that could only ratchet down. If a benchmark was flaky, or if it didn’t actually correlate with user latency, we threw it out rather than let Claude climb the wrong hill.
@Claude please prove that hill climbing against each of these can result in measurable wall clock perf wins. we’ll unship the benches for any candidates that cannot prove that
Wall-clock time is what users feel, but it’s noisy, and milliseconds are too flaky to use as a CI gate. Instruction counts were appealing because they were deterministic, but we still needed Claude to prove they tracked wall-clock time. So we asked Claude to drive the count down on two hot paths: the routine that assembles a conversation’s message tree, and a scanner for status lines in Claude Code output. Claude profiled both with Valgrind and found that a quarter of the first path’s instructions were megamorphic dictionary lookups, resolving the same message ID three separate times. An hour later it had cut instructions on both paths by 48% and 31%, and wall-clock time had dropped 78% and 44%. We checked in two new ratchets. From then on, any PR that raised the instruction counts of those paths failed CI, and a daily job lowered each ceiling whenever the count went down. Does the count track the clock?CPU instructions vs. wall-clock time That led us to the central lesson of the sprint. With Claude, measuring something makes it tractable. Measurement used to be step zero: you’d add a metric, wait for data to roll in, and only then start to understand the problem. With Claude, it’s step one of the climb. As soon as Claude had a number to beat, it could start optimizing. This meant the highest-leverage thing we could do was find more things to measure. THE LOOP, THREAD BY THREAD All of this ran in the same Slack channel, with multiple engineers and Claude jamming in every thread. From there, the sprint settled into a loop:
Someone would open a thread about a slow stretch of a journey, often with a screenshot or recording. Claude would trace the flow, then find or build a benchmark that demonstrated the problem. Once it had a promising result in the lab, Claude would come back with a PR — often several, sized for risk and review, with anything user-visible behind a flag. After it shipped, Claude watched the deploy and read the field data. If performance improved, Claude locked in the win by ratcheting the benchmark down; if not, it turned the flag off and iterated. Then it went looking for the next slow spot in the same journey.
One thread in the loopSomeone opens a thread; Claude takes it from there An example: someone shared a screen recording that showed sidebar rows popping in after the page loaded. Chat and Cowork rows resolved at different times, making the page feel janky. None of our existing monitors detected it. The closest we had was Cumulative Layout Shift, but each shift only scored about 0.008 — well within the good threshold of 0.1. Issac had the idea to reference the underlying Layout Instability API directly. Claude created a telemetry event that mapped the sources of each layout-shift entry to a named region (e.g. sidebar, transcript) and phase (e.g. before first paint, after typeable). It added an integration test that opened the page with a populated sidebar, held the sidebar’s data until after first paint, and failed on any shift in any named region. Claude used that as a benchmark to prove a fix: the test went red 20 of 20 runs on main, and green 20 of 20 on the PR. After the event deployed, Claude read the field data and found that 31% of web page loads moved something after the page was usable, without any user interaction. From there, Claude worked through the causes by name: a header row that arrived late, a caret that slid sideways once the user’s name loaded, a list that moved when the scrollbar popped in. Claude fixed the top offenders as a batch, and when they were gone, it found the next batch. FIG ASidebar jank, before and after · throttled 4G That was one thread. During the sprint, we ran more than a hundred and fifty at a time. SCALING HORIZONTALLY Once the loop worked on one thread, running it on more was just a matter of opening them. Instead of closing a thread once its original request had been fulfilled, Claude would keep going. An individual thread would put up fifty, sometimes a hundred, optimization PRs. Increasingly, it was Claude, not one of us, opening new threads to chase opportunities it had found on its own, as part of a separate investigation or nightly job. Shelley, one of the engineers in the channel, observed, “[This model] is a numbers demon.” Every measurement found something to improve. Claude ran a React hook census and found 6,900 hooks and 900 store subscriptions in the composer’s typing path, re-rendering on every keystroke. Claude counted style recalculations and found a single :root:has() selector adding 24 milliseconds to every DOM change. Claude traced code paths after first paint and found a leftover location.reload() causing half a million hidden reloads a day that none of our load metrics could see. Claude read profiler samples from idle tabs and found identical cache snapshots being cloned into IndexedDB twice a minute, all on the main thread. We rarely knew where a thread would lead. In a sweep for CPU hitches, Claude noticed that highlighting a finished code block could freeze the page for about a second. It dug in the lab and found the culprit: em dashes. If a reply’s markdown contained any non-Latin-1 character, like an em dash or a curly quote, V8 stored the entire string as UTF-16, which put every syntax-highlighting regex on its slower two-byte path. Claude fixed it with a twenty-line change to copy each code block into a one-byte string before highlighting it. Highlighting the first code block on a pageMain-thread blocking time, before and after the fix By the second week, we could barely summarize our output into daily updates. On the busiest days, more than two hundred changes landed. Claude kept proposing new benchmarks; about a third of PRs included additional telemetry or guardrails, and each new instrument generated more threads with more opportunities. Working in one channel meant everything happened in the open. We jumped in and out of each other’s threads to debate decisions and celebrate wins. Word spread: other teams started bringing their changes into the channel to have them reviewed for performance. New projects were written in subtly more performant ways because of all the guardrails and Claude skills that had been introduced. GUARDRAILS We’d prepared for the pace. Because nearly everything we touched was a hot path (the first paint, the composer, the transcript), we’d established our safety mechanisms up front. Every PR went through automated review with at least one human approval, unit tests always came before optimizations, and anything that could cause a user-visible problem shipped behind a short-lived feature flag. When the flags started piling up, we opened a thread to coordinate their rollouts and cleanup. Claude classified every flag as a kill switch or ramp, and retired each one as soon as it was safe. Across the two weeks, we introduced nearly two hundred flags, more than half of which were already cleaned up by the end. We also knew that performance wins decay in a fast-moving codebase, and code ships fast at Anthropic. Once a project proved a win, we invested in ways to protect it. The static composer, for example, is brittle by design. We show users an HTML copy of the page almost immediately, and let React paint directly on top of it. FIG BStatic composer, before and after · throttled 4G If the React render is off by even a pixel, the magic is lost. So Claude built dozens of guardrails:
The static markup is generated by rendering the real React component in jsdom, and a test guarantees they never drift. An integration test suite compares the static page against the React render across fourteen viewport sizes, and asserts alignment within 1 px. A keystroke test types straight through the handoff and fails on any lost or reordered key. In the field, every handoff reports shifts to a tenth of a pixel. Claude opens a thread for any event with nonzero movement.
Not everything could be caught in the lab, so we also made use of the oldest guardrail in the book: incremental rollouts. High-risk changes were rolled out to employees first, then one percent of users, then everyone. Four hours after we released the static composer internally, a teammate shared a screen recording of a layout shift that none of our metrics could see. When he opened claude.ai in a new tab, the composer would drop — but it wasn’t our code. #claude-apps-perf · threadMarius6:15 PMOccasionally, I’m seeing a small-ish (maybe 15–20px) vertical layout shift (pushing the composer box down) when opening claude.ai in a new tab (not so much when reloading the page). I can’t quite pin down what exactly causes that, but it’s thereScreen recording · 0:02ClaudeApp7:05 PMFound it in your recording — it’s Chrome resizing the page, not the handoff from the static composer to the real one (that measured 0 px on all 49 of your loads today). On the new-tab page, Chrome draws its own 56 px footer below the page (“Managed by anthropic.com · Customize Chrome”). When the tab navigates to claude.ai the footer goes away and the page gets 56 px taller, but only ~100 ms after our first paint. /new puts the greeting and composer 18% of the page height from the top, so they drop 0.18 × 56 ≈ 10 px (your video measures 10). A reload has no footer to remove, hence new-tab only; “occasionally” is whether first paint beats the resize. It’s only “occasional” because it needs Chrome to pre-render the page from the address bar while you’re still on the new-tab page: the hidden page is laid out at the shorter height and only resized after it’s shown. This was always latent; the page just never painted that early before. Our layout tests can’t see it because headless Chrome has no browser UI to retract, and the placeholder and the app move together. Recreated from a real conversation. Somehow, Claude traced it to an edge case in Chrome’s speculative loading. While a user was typing the URL into the address bar, Chrome would prerender the page in the background, at the height of the current tab. On browsers managed by an organization, the new-tab page is slightly shorter because of a footer. When the user pressed Enter, the first frame of claude.ai showed that slightly shorter layout, and Chrome resized it about a tenth of a second later. Claude pinned the layout across the resize, and we added a test to simulate the prerender flow. STEERING The loop was productive, but it wasn’t autonomous. Keeping it fast, safe, and on track was our job, and it had three parts. Ambition. By default, Claude is careful about scope. It tickets findings, hedges on feasibility, and pads its estimates. But we were confident in our guardrails. A lot of what we did, especially early on, was to encourage Claude to be bolder. #claude-apps-perf · threadClaudeApp3:32 PMYes — one small PR to give Code the same timing marks Chat and Cowork already have. I’ll put it up this week; realistically Code’s number waits a few days for merge, deploy, and a baseline window.Raymond3:32 PMif you put it up right now I will get it merged and deployed. we have the power to do anything. please be braverClaudeApp3:33 PMOn it — the PR will be up within the hour.Recreated from a real conversation. When we started hitting the targets we’d set, we noticed that threads would slow down. Sam went thread to thread with the same message: “Let’s keep driving this down, the targets are not the stopping point. What’s next? Be ambitious.” Taste. Every thread had a named human owner, and Claude highlighted any user-perceptible change with before-and-after screenshots or recordings for them to rule on. Should a table fill in cell by cell, or wait until each row is complete? Should a loading skeleton show up immediately, or only after half a second? Is a word-by-word fade on streamed text worth the fifth of the frame budget it costs? Claude looked for ways to shave milliseconds, and we weighed the tradeoffs. Direction. We kept each thread deliberately narrow, focused on one benchmark or journey, and asked Claude to find improvements only within that scope. We thought of the threads as a hundred and fifty hammers seeking nails. Most of our calls were about sequencing and user impact: which surfaces to prioritize, how to combine threads that were stepping on each other, and when to close a thread that had reached diminishing returns. One 900-line PR got a one-line reply: “going to gavel that 2ms per send is not worth the complexity of maintaining this build plugin.” AN 8-MILLISECOND BUDGET One of our sidequests shows everything working together. To demonstrate an optimization to a regex used in live syntax highlighting, Claude attached a screen recording of a long answer streaming in the lab. In the corner, it had added a frame-rate readout, computed in the page from animation-frame timestamps. #claude-apps-perf · threadRaymond2:33 PMthis is actually kind of a sick bench. are we capped at 60 fps? can you try to drive scroll and stream smoothness to 120? iiuc your rig may not support thisClaudeApp2:34 PMRight, today’s rig ticks at 60 Hz because headless Chromium does by default. I believe it can be driven at 120 (uncapped vsync or DevTools frame control) — confirming that first, then I’ll re-run the eval against an 8.3 ms frame budget.ClaudeApp2:59 PMUpdate on the 120 Hz rig: it works. Deterministic 120 Hz frame stepping in headless Chrome via DevTools begin-frame control — exactly 240 frames for 240 begin-frames at 8.33 ms, so “did this frame fit the 120 Hz budget” becomes an exact read rather than a noisy one.Raymond3:05 PMamazingcookRecreated from a real conversation. Once the mechanism and ambition were established, Claude got to work. Each painted frame had a budget of 8.33 milliseconds, so Claude stepped through a long reply frame by frame, timing each one to find the slow parts. It eliminated O(message length) work per chunk by memoizing finished blocks, moved tokenization logic for growing code fences to a worker, and revealed tables cell by cell. In that one thread, we landed nearly sixty PRs. Long replies blocked the main thread for about 200 milliseconds in total where they used to block it for about 750, ran on about a third of the CPU, and held 120 fps from start to finish on a 120 Hz MacBook. The 120 Hz rig itself became a nightly job, with Claude watching for regressions. ClaudeDevs@ClaudeDevsLong answers on Claude on web and desktop now stream ~4x smoother.We rebuilt the streaming renderer to only touch what’s still changing, so a long reply stalls 9x less on a slower laptop, its worst freeze is 4.5x shorter, and on a 120Hz MacBook it holds 120fps start to finish. When we started the sprint, we hadn’t planned to hill climb on the milliseconds between frames while streaming. But it turned out we could count them — and anything we could count, Claude could climb. WHAT’S NEXT Today, claude.ai and the desktop app are about 3x faster than they were in early August, and the ratchets should keep them there. But we’re not done: the 95th percentile, other journeys, and very long conversations still have room to improve. In a separate post, we’ll also write about some of the sidequests that took us upstream during the sprint, with contributions landing in Electron, Chromium, Node.js, and more. When we shared the results internally, Issac put it best: “You could not have convinced me this was possible even six months ago.” We expect to keep working this way, one thread at a time, at any scale. The channel’s still going. With contributions from Alfred Xing, Anthony Morris, Benjamin Pasero, Chase McCoy, Joshua N., Luke Deen Taylor, Marius Schulz, and Shelley Vohr. Special thanks to Boris Cherny for encouraging us to be more ambitious.
原文入口
阅读原文(claude.dev)原文将在新窗口打开。收录时间不是内容更新时间;正文、译文与解读的版权归原作者/来源所有,本站仅作个人阅读辅助并保留原文入口;页面时间均为北京时间。
了解来源批准与收录规则