跳到正文
Claude.dev 开发者博客· Raymond Wang, Sam Attard, and Issac G.·· 12 天前精选AI 评分74

我们如何在两周内让 claude.ai 提速 3 倍

How we made claude.ai 3x faster in two weeks

AI 导读

Anthropic 团队用两周冲刺把 claude.ai 和 Claude 桌面应用的核心体验提速约 3 倍,p75 下全新加载可输入时间从 3.1 秒降到 0.55 秒,启动 Claude Code 会话从 0.8 秒降到 0.3 秒,加载 Claude Cowork 云会话从 2.6 秒降到 0.73 秒。

推荐理由

Anthropic 团队复盘用 Claude 在两周内把 claude.ai 提速约 3 倍,可看到以测量驱动优化的具体工程方法。

正文 · 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(测试版),运行一个内部研究模型,大致相当于 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 · thread

  1. Sam12:34 PM

    除了挂钟计时,我们还能做什么?比如,我们可以测量 JS 指令数吗?

  2. ClaudeApp12:42 PM

    可以。对于纯 JS 热路径,可以测量字面指令数:在 Valgrind 下用 node --predictable 运行基准测试,并与已检入的基线进行比较——一次运行,无需统计。

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

  3. Sam12:49 PM

    让我们在一个线程中探索 valgrind + Ir + --predictable,并在新线程中分别探索每个浏览器/React 基准测试。在所有线程中都 ping 我。你知道我们想要什么。开始吧

根据真实对话重新创建。

十一分钟后,五个线程正在运行,每个都专注于不同的测量:指令数、V8 调用次数、React 提交次数、样式重计算次数和 DOM 变更次数。

我们对每个新基准测试都持一些怀疑态度。每个基准测试都有两项任务:第一,一个 Claude 可以在实验室中推动的指标;第二,CI 中的一个护栏,其数字只能逐步降低。如果某个基准测试不稳定,或者它实际上与用户延迟不相关,我们就会将其丢弃,而不是让 Claude 爬错山。

@Claude 请证明针对这些中的每一个进行爬坡都能带来可衡量的挂钟性能提升。对于任何无法证明这一点的候选基准测试,我们将取消发布

挂钟时间是用户能感受到的,但它很嘈杂,毫秒太不稳定,无法用作 CI 门禁。指令数很有吸引力,因为它们是确定性的,但我们仍然需要 Claude 证明它们能跟踪挂钟时间。

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

一小时后,它将两条路径的指令数分别减少了 48% 和 31%,挂钟时间分别下降了 78% 和 44%。我们检入了两个新的棘轮。从那时起,任何提高这些路径指令数的 PR 都会导致 CI 失败,并且每当计数下降时,每日任务都会降低每个上限。

计数能跟踪时钟吗?

CPU 指令 vs. 挂钟时间

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

过去,测量是第零步:你先添加一个指标,等待数据流入,然后才开始理解问题。有了 Claude,测量成了攀登的第一步。一旦 Claude 有了一个要超越的数字,它就能开始优化。这意味着我们能做的最高杠杆的事情就是找到更多可测量的东西。

循环,逐线程进行

所有这些都在同一个 Slack 频道中运行,多名工程师和 Claude 在每个线程中一起协作。从那里开始,冲刺进入了一个循环:

  1. 有人会打开一个关于旅程中某段缓慢部分的线程,通常附有截图或录屏。
  2. Claude 会追踪流程,然后找到或构建一个能证明问题的基准测试。
  3. 一旦在实验室中有了有希望的结果,Claude 会带着一个 PR 回来——通常有多个,根据风险和审查规模调整,任何用户可见的内容都放在标志后面。
  4. 发布后,Claude 会观察部署并读取现场数据。
  5. 如果性能有所改善,Claude 会通过降低基准测试来锁定胜利;如果没有,它会关闭标志并迭代。
  6. 然后它会在同一旅程中寻找下一个缓慢点。

循环中的一个线程

有人打开一个线程;Claude 从那里接手

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

Issac 有了直接引用底层布局不稳定性 API的想法。Claude 创建了一个遥测事件,将每个 layout-shift 条目的 sources 映射到命名区域(例如侧边栏、转录)和阶段(例如首次绘制前、可输入后)。它添加了一个集成测试,打开带有已填充侧边栏的页面,将侧边栏的数据保持到首次绘制之后,并在任何命名区域发生任何偏移时失败。Claude 以此作为基准来证明修复:该测试在 main 上 20 次运行中 20 次变红,在 PR 上 20 次运行中 20 次变绿。

事件部署后,Claude 读取现场数据,发现 31% 的网页加载在页面可用后移动了某些东西,而没有任何用户交互。从那里,Claude 按名称逐一解决原因:一个迟到的标题行,一个在用户名字加载后横向滑动的光标,一个在滚动条弹出时移动的列表。Claude 将最主要的违规者批量修复,当它们消失后,它找到了下一批。

Side-by-side recording of the claude.ai sidebar loading. Before, the rows arrive late and rearrange: ten rows jump, nine appear and four vanish. After, the rows fill in where they will stay, and nothing moves.

图 A侧边栏卡顿,前后对比 · 限速 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 注意到高亮一个已完成的代码块会让页面冻结约一秒。它在实验室里深挖,找到了罪魁祸首:em dash(长破折号)。如果一条回复的 markdown 中包含任何非 Latin-1 字符,比如 em dash 或弯引号,V8 就会把整个字符串存为 UTF-16,这会让每一个语法高亮正则表达式都走上更慢的双字节路径。Claude 用二十行的改动修复了它:在给每个代码块做高亮之前,先把它复制成一个单字节字符串。

高亮页面上的第一个代码块

主线程阻塞时间,修复前后对比

到第二周,我们几乎无法把产出总结成每日更新。最忙的日子里,有两百多项变更落地。Claude 不断提出新的基准测试;大约三分之一的 PR 包含了额外的遥测或防护栏,而每一个新仪表又产生了更多线索和更多机会。

在同一个频道里工作意味着一切都公开进行。我们跳进彼此的话题中,争论决策、庆祝胜利。消息传开了:其他团队开始把他们的变更带进频道,让它们接受性能审查。新项目在写法上微妙地变得更高效,因为引入了所有这些防护栏和 Claude 技能。

防护栏

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

当开关开始堆积时,我们开了一个话题来协调它们的发布和清理。Claude 把每个开关归类为终止开关或渐进开关,并在安全后立即将其退役。在这两周里,我们引入了近两百个开关,其中超过一半在结束时已经被清理掉。

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

Side-by-side recording of a fresh load of claude.ai. Before, the page stays blank and the composer takes input at 2.93 seconds. After, a static greeting and composer take input at 0.36 seconds; the user types a message, and the text is kept when the real composer fades in at about 3 seconds.

图 B静态输入框,前后对比 · 限速 4G

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

  • 静态标记通过在 jsdom 中渲染真实的 React 组件来生成,并有测试保证两者永不偏离。
  • 一套集成测试套件在十四种视口尺寸下将静态页面与 React 渲染结果进行对比,并断言对齐误差在 1 像素以内。
  • 按键测试会直接贯穿整个交接过程进行输入,任何按键丢失或顺序错乱都会导致失败。
  • 在实际环境中,每次交接都会报告低至十分之一像素的偏移。Claude 会为任何非零移动的事件开启一个线程。

并非所有问题都能在实验室中捕捉到,因此我们还使用了最古老的防护措施:增量发布。高风险变更先向员工发布,然后向百分之一的用户发布,最后向所有人发布。我们在内部发布静态输入框四小时后,一位队友分享了一段布局偏移的屏幕录制,而我们的任何指标都无法看到这个问题。当他在新标签页中打开 claude.ai 时,输入框会下沉——但这不是我们的代码问题。

#claude-apps-perf · 线程

  1. Marius下午6:15

    偶尔,我在新标签页中打开 claude.ai 时会看到一个较小的(大约 15–20px)垂直布局偏移(将输入框向下推)(重新加载页面时不太明显)。我无法确切定位是什么原因导致的,但它确实存在

    屏幕录制 · 0:02

  2. ClaudeApp下午7:05

    在你的录制中找到了——这是 Chrome 在调整页面大小,而不是从静态输入框到真实输入框的交接(在你今天所有 49 次加载中,该交接测量为 0 px)。

    • 在新标签页上,Chrome 会在页面下方绘制自己的 56 px 页脚(“由 anthropic.com 管理 · 自定义 Chrome”)。当标签页导航到 claude.ai 时,页脚消失,页面增高 56 px,但仅在我们首次绘制后约 100 ms。
    • /new 将问候语和输入框放置在距页面顶部 18% 页面高度的位置,因此它们会下沉 0.18 × 56 ≈ 10 px(你的视频测量为 10)。重新加载没有页脚需要移除,因此仅在新标签页中出现;“偶尔”取决于首次绘制是否赶在调整大小之前。
    • 它之所以“偶尔”出现,是因为需要 Chrome 在你仍处于新标签页时从地址栏预渲染页面:隐藏页面以较短的高度进行布局,仅在显示后才调整大小。
    • 这个问题一直潜伏着;只是页面之前从未那么早绘制过。我们的布局测试无法看到它,因为无头 Chrome 没有浏览器 UI 需要收回,而且占位符和应用会一起移动。
根据真实对话重现。

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

引导

这个循环富有成效,但并非自主运行。保持它快速、安全且不偏离轨道是我们的职责,这包括三个部分。

雄心。默认情况下,Claude 对范围很谨慎。它会将发现记录为工单,对可行性持保留态度,并夸大其估算。但我们对我们的防护措施充满信心。我们所做的很多事情,尤其是在早期,都是为了鼓励 Claude 更加大胆。

#claude-apps-perf · 线程

  1. ClaudeApp下午3:32

    是的——一个小 PR,让 Code 拥有 Chat 和 Cowork 已有的相同时间标记。我本周会提交;实际上 Code 的数字要等几天才能合并、部署和建立基线窗口。

  2. Raymond下午3:32

    如果你现在就把它提交上去,我会把它合并并部署。我们有能力做任何事。请更勇敢一点

  3. ClaudeApp下午3:33

    马上办——PR 会在一小时内提交。

根据真实对话重现。

当我们开始达成自己设定的目标时,我们注意到线程会变慢。Sam 逐个线程地传达同一条信息:“让我们继续把它压下去,目标不是终点。下一步是什么?要有野心。”

品味。每个线程都有一位具名的负责人,Claude 会用前后对比截图或录屏标出任何用户可感知的变化,供他们裁定。表格应该逐格填充,还是等整行完成后再显示?加载骨架屏应该立即出现,还是半秒后才出现?流式文本的逐字淡入是否值得花掉五分之一的帧预算?Claude 寻找削减毫秒的方法,我们权衡取舍。

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

8 毫秒的预算

我们的一个支线任务展示了所有部分如何协同工作。为了演示对实时语法高亮中所用正则表达式的优化,Claude 附上了一段在实验室中流式输出长回答的屏幕录制。在角落里,它添加了一个帧率读数,在页面中根据动画帧时间戳计算得出。

#claude-apps-perf · thread

  1. Raymond下午2:33

    这其实是个挺厉害的测试台。我们是被限制在 60 fps 吗?你能试着把滚动和流式输出的流畅度推到 120 吗?据我所知你的机器可能不支持这个

  2. ClaudeApp下午2:34

    对,今天的测试台以 60 Hz 运行,因为无头 Chromium 默认如此。我相信它可以被驱动到 120(不限制 vsync 或用 DevTools 帧控制)——先确认这一点,然后我会针对 8.3 毫秒的帧预算重新运行评估。

  3. ClaudeApp下午2:59

    关于 120 Hz 测试台的更新:成功了。通过 DevTools 的 begin-frame 控制在无头 Chrome 中实现确定性的 120 Hz 帧步进——240 个 begin-frame 恰好对应 240 帧,每帧 8.33 毫秒,因此“这一帧是否满足 120 Hz 预算”变成了精确读数,而非有噪声的读数。

  4. Raymond下午3:05

    太棒了

    开干

根据真实对话重现。

一旦机制和野心确立,Claude 就开始工作。每个绘制帧有 8.33 毫秒的预算,因此 Claude 逐帧地检查一段长回复,为每一帧计时以找出慢的部分。它通过记忆化已完成的块消除了每个块的 O(message length) 工作,将不断增长的代码围栏的分词逻辑移到 worker 中,并逐格显示表格。

在那一个线程中,我们合并了近六十个 PR。长回复阻塞主线程的总时间从大约 750 毫秒降至约 200 毫秒,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 说得最好:“六个月前,你不可能说服我这是可能的。”我们期望继续以这种方式工作,一次一个线程,无论规模大小。这个频道仍在继续。

感谢 Alfred Xing、Anthony Morris、Benjamin Pasero、Chase McCoy、Joshua N.、Luke Deen Taylor、Marius Schulz 和 Shelley Vohr 的贡献。特别感谢 Boris Cherny 鼓励我们更加雄心勃勃。

来源:Claude.dev 开发者博客 · claude.dev