前阵子我在一个 Claude Code 自动化任务里又踩了一次性能的坑:一轮指令发出去,要等十几秒才看到输出,期间 CPU 占用不高、内存也没波动,界面看起来一切正常。我当时的第一个念头很典型——是不是上下文塞太满了?是不是 prompt 写得不够简洁?于是花了半小时把 prompt 从三段压成一段,把不需要的文件从上下文里清掉,结果好了一天,隔天又卡成老样子。
后来我才意识到,我从头到尾都在“猜”,根本没有“量”过。
那之后我把这套流程彻底改掉了:先测量,再优化。不管是在 Claude Code 里调一个自动化脚本,还是排查某个 API 服务的响应延迟,我都先拿数据说话。今天这篇就想跟你聊聊这套“先测量再优化”的方法论——它不挑语言、不挑框架、不挑工具,尤其适合 Claude 这类场景里“说不清哪里慢但就是慢”的玄学问题。
1. 为什么性能优化必须先测量:三个真实翻车场景
1.1 猜测型优化的三个典型翻车现场
“猜哪里慢”听起来很符合直觉,毕竟我们是工程师,对系统多少有些体感。但体感这玩意,在性能优化里是最不可靠的。我见过太多人(包括我自己)栽在这三个坑里。
第一个坑是优化了个伪瓶颈。系统整体耗时 10 秒,你猜是模型响应慢,于是换了提示词、调了参数,结果耗时变成 9.5 秒,感觉有点用但不多。而真实情况可能是:模型只花了 2 秒,剩下 8 秒全耗在本地文件读取和工具调用上。你改动的那部分,压根不是瓶颈。
第二个坑更隐蔽——破坏性修复。你凭感觉觉得“上下文太长导致变慢”,于是把一个本来有用的背景信息从 prompt 里删掉了。快是快了一点,但生成质量明显下降,任务开始答非所问。性能优化变成质量事故,这不是赚了,是亏了。
第三个坑是时间黑洞。猜不准就得反复试,每次试完还得等效果反馈。周旋两天,改动七八处,最后也不知道到底是哪一处起了作用。这种状态下做出的优化,没法沉淀经验,换一个项目就得从头再来。
1.2 测量为什么能治好“性能焦虑”
测量能提供三样东西,正好是三副解药。
第一是基线。没有基线,你连“优化了没有”都说不清。拿固定任务跑三轮,记录耗时,这就是基线。之后任何改动,都拿新数据和基线对比,是升是降一目了然。
第二是定位。测量把黑盒打开一个口子:是整个请求慢,还是某个子步骤慢?是模型推理慢,还是工具调用慢,还是网络等待慢?每一层都有自己的耗时数据,拿着数据找问题,比凭空猜靠谱得多。
第三是反馈闭环。改完一个地方,立刻重新测量,用同一套测试方法验证效果。有效就留,无效就回滚。这个循环跑顺之后,性能优化不再是“玄学调参”,而是一步一个脚印的工程。
2. 别只看总耗时:Claude 场景下必须测量的 4 类指标
2.1 延迟类指标:先分清是“等”还是“算”
很多人优化性能,只看一个总耗时。总耗时当然重要,但它给不了定位线索。在 Claude 这类交互场景里,我建议先把延迟拆成两维度看。
第一维度是“等”和“算”的区分。你发出一个请求,到收到首个 token,中间有网络传输时间、服务端排队时间、模型推理时间;收到首个 token 到完整输出结束,又是另一段。拿一个比喻来说:你去餐厅等菜,总耗时 = 等座位 + 等服务员下单 + 等厨房做菜 + 等传菜上桌。你不量每个环节的时间,只知道“这顿饭吃了四十分钟”,那到底该催前台还是催后厨,全靠猜。
第二维度是分位数而不是平均值。服务的平均响应时间是 2 秒,听起来不错?但如果你看 p95,可能已经是 8 秒,说明有大量请求在尾部拖跨了体验。测量延迟类指标时,至少记录 p50、p95、p99 三个分位。
2.2 消耗类指标:Token 就是你的“时间预算”
在 Claude 场景里,Token 消耗是一个绕不开的指标。它不是钱的问题,而是时间的问题——每次请求发送的 token 越多,服务端需要处理的输入就越长,首字延迟和整体耗时通常都会更高。
测量时重点看两类 Token:输入 Token(prompt 部分)和输出 Token(模型生成部分)。如果你发现某一轮任务输入 Token 高达几万,那你要想办法精简上下文;如果输出 Token 里有大量格式重复、结构化冗余的文本,那就要考虑控制输出长度,或者调整指令让它精炼作答。
除此之外,缓存命中率也值得关注。Claude 类服务通常有 Prompt Caching 机制,命中缓存的部分能大幅减少重复处理时间。如果你发现同一段很长的上下文在每轮任务里都被反复发送且没有触发缓存,这就是一个明显的优化信号:调整上下文组织方式,让公共部分稳定复现,命中缓存。
2.3 资源类指标:别让本机进程拖后腿
刚开始排查时,我犯过最蠢的错误:把问题全推给远端模型,结果发现是本机的 Claude Code 进程在疯狂占用磁盘 IO。
本地工具型应用(比如 Claude Code)尤其要盯几类资源:
- CPU:进程是否长期满核?某个 Python 子进程在空转?
- 内存:是否持续增长?有没有泄漏趋势?
- 磁盘 IO:是不是每次请求都重新扫描整个目录树?
- 网络 RTT:请求发出到收到响应的纯网络时间是多少?
其中网络 RTT 很容易被忽略。很多“卡顿”其实是网络抖动,不是模型变慢。你在测量时加上这个指标,能直接排除一大类干扰因素。
2.4 业务类指标:慢不一定等于差
最后一类是业务指标,这个也经常被忽略。性能优化的终极目标不是“快”,而是“在合理时间内把事情做完”。
以 Claude Code 为例,几类业务指标值得记录:
- 任务完成率:这一轮指令是否成功完成?
- 重试次数:同一个操作被工具执行了几次?
- 上下文利用率:传入的上下文里,真实被模型用到的比例有多高?
- 无效输出率:模型是否在规定格式外输出了大量无关内容?
有时候系统“慢”是因为它在反复纠错、反复重试。把这类指标测出来,你就知道该优化的不是速度,而是准确性。
3. 怎么测:一套从零到一的测量流程
3.1 先把基线跑出来:一套可复现的测试场景
测量的第一步不是选工具,而是定义“测什么场景”。我强烈建议你准备一套固定的测试任务,比如:
让 Claude Code 读取一个项目里的三个指定文件,总结它们的共性,并输出一份两段式摘要。全程不额外干预。
这个任务要满足三个要求:可复现(每次执行的工作内容一致)、有一定复杂度(能反映真实场景)、耗时在可观察范围内(建议 30 秒到 3 分钟,太短的容易受噪声干扰)。
然后跑至少 5 轮,记录每一轮的总耗时、各子步骤耗时、Token 消耗。5 轮数据取中位数,就是你的基线。别只跑一遍就下结论,首轮大概率包含冷启动和缓存加载,不代表真实水平。
我现在的习惯是:任何性能优化启动之前,先把这个基线跑出来,然后完完整整地存档。后续所有改动,都以这个存档为参照物。
3.2 手头常用的四类测量工具
测量工具不一定要很重,够用就行。我按场景给你分个类。
第一类是命令行计时工具。最基本的就是系统自带的 time 命令(或者更详细的 /usr/bin/time -v),跑任何一个脚本、命令之前套上它,就能拿到执行耗时和资源峰值。虽然粗糙,但零成本、零侵入,适合快速摸底。
第二类是应用自身的日志和调试开关。Claude Code 这类工具通常提供了日志输出或调试模式。打开 verbose 或 debug 级别日志之后,你能看到每轮交互的请求时间、响应时间和中间的工具调用记录。这比你自己埋点省事得多,我每次排查第一件事就是翻日志。
第三类是通用性能剖析工具。比如 py-spy(Python 进程的采样分析器)、perf(Linux 性能剖析)、dtrace(macOS/BSD 环境)。这类工具能告诉你“CPU 时间到底花在哪个函数里”,适合定位本地进程内部的性能热点。
第四类是网络/服务观测工具。如果你要验证 API 调用延迟,可以用简单的请求计时脚本,也可以上专门的观测平台(比如 Prometheus + Grafana 方案)。对于大多数个人项目和中小团队,前面三类已经够用了,没必要一上来就搭整套观测体系。
3.3 现场记录的实操技巧:别让脏数据骗了你
测量这件事,听着简单,做起来全是细节。几个我踩过的坑,直接说重点。
样本量要够。至少 10 轮有效数据。前 2 轮通常要丢弃,因为它们可能包含冷启动、DNS 解析、缓存加载等环境因素。
环境要隔离。测量期间不要开着视频、不要跑大文件下载、不要同时开着重型编译任务。本地磁盘 IO 和 CPU 会被这些无关负载污染,测出来的数据失真。
对象要一致。如果你对比的是优化前后,那保证两者跑的任务完全一样、上下文内容完全一样、输入文件完全一样。变量越少,结论越可信。哪怕只是顺手改了一个 prompt 标点,都会污染对照组。
记录要原始。我习惯把所有轮次的数据原样写进一个表格里,不提前剔除“看起来不正常”的离群值。先保留,再分析。离群值往往藏着问题,比如某一次网络抖动、某一次缓存失效,这些信息很值钱。
3.4 数据怎么读:从数字到结论的四个步骤
拿到一堆数字之后,怎么转化成优化决策?我有一套固定流程。
第一步,看分布而不是看单个数字。把 10 轮耗时的中位数和最大最小值标出来,感受数据的离散程度。离散程度大,说明系统不稳定,可能是网络抖动或缓存状态不一致,这时候谈平均优化没意义。
第二步,做分层对比。总耗时 = 本地预处理 + 请求发送 + 模型推理 + 工具执行 + 结果解析,一层一层加起来,看哪层占比最大。占比最大的那一层,就是你要决定优化方向的层。
第三步,找相关性。比如发现“工具调用次数”和“总耗时”高度正相关,那优化方向就明确了:减少工具调用次数。又比如发现“输入 Token 数”和“首字延迟”强相关,那精简上下文就是线索。
第四步,形成可实验的假设。测量给你方向,但最终还是要靠实验确认。比如“把上下文从 5000 token 压到 2000 token,总耗时应该下降 30%”——这是一个清晰、可验证的假设。下一步就是动手改,改完重新测量。
4. 测出瓶颈之后:四种典型模式与对应优化策略
4.1 模式一:上下文膨胀型
这类问题的测量特征非常明显:输入 Token 居高不下,请求发出到首个响应之间的耗时偏长,本地机器资源正常,网络正常。
上下文膨胀通常不是单个文件太大,而是累积效应。Claude Code 在工作时会把读取过的文件、历史对话、系统提示都留在上下文窗口里。任务越复杂,历史信息越多,上下文越膨胀。到某个临界点之后,每轮请求都要处理大量重复且低效的输入。
优化策略分三路走。第一路是精简:把 prompt 里冗余的背景描述删除,只保留任务相关的必要信息。第二路是拆分:一个大任务拆成多个小任务,每个子任务只携带自己需要的上下文。第三路是隔离:用子代理或者独立会话处理局部任务,避免主上下文被中间过程污染。这三路可以叠加使用,优化空间往往很大。
4.2 模式二:工具调用循环型
测量特征:模型推理耗时不高,但工具执行步骤很多,总耗时被大量“小步慢跑”拖长。日志里能看到同类型工具被反复调用,比如反复读取同一个文件、反复执行同一类查询。
这种问题的本质是“计划不够”,模型在走一步看一步,缺少批量处理意识。优化策略是主动给模型提供合并路径。比如在指令里明确写“一次性读取 A、B、C 三个文件,不要逐个读取”;或者在工具层加一个批量读取接口,让模型一次拿到全部数据。还可以给常用查询结果加缓存,避免跨轮次重复计算。
实操中我发现一个规律:Claude 这类模型非常擅长执行清晰的批量指令,但对“默认希望它聪明地自己合并”这件事并不总是靠谱。作为使用者,你得在任务设计阶段就把合并逻辑讲清楚。
4.3 模式三:网络等待型
测量特征:本地进程 CPU 空闲、磁盘无波动,但请求耗时曲线和网络 RTT 曲线同步起伏。这类问题在新手期尤其容易被误判成“模型慢”。
处理网络等待的第一步不是换网络,而是增加本地侧的数据复用。把需要反复读取的远程资源拉到本地缓存;把静态内容提前下载;把可并行的数据请求合并成一次。减少请求次数,比优化单次请求的网络质量要稳得多。
第二步才是考虑网络环境本身。但对于工具使用者来说,能做的有限,重点还是通过缓存和批量请求降低对网络的依赖。
4.4 模式四:模型生成体量过大型
测量特征:输出 Token 数量异常高,响应总时间长,但首字延迟其实不算慢。换句话说,模型“开讲”很快,但“讲太多”导致整体拖沓。
这类问题的根因多半是输出侧缺少约束。模型默认会生成详尽回答,除非你明确告诉它“简体输出”或“只给结论”。优化策略很简单:在指令里加输出格式限制,比如“200 字以内”“只输出 JSON”“列三个要点即可”。此外还可以利用参数约束最大输出 Token 数,从机制上封住上限。
我见过一个很夸张的案例:一个本来 30 秒能完成的任务,因为某次指令描述让模型展开了大量额外解释,硬生生跑了两分钟。加上输出约束之后,直接回到 35 秒。这类优化的性价比极高,基本是零成本改参数。
4.5 优化效果的验证:你必须做二次测量
优化完不是结束,是测量的第二个起点。把优化前的基线和优化后的新数据放到同一个表格里,逐项对比。
重点看这几项:总耗时是否下降、p95 是否同步下降、Token 消耗是否减少、任务完成率是否保持不变或变好。这里要特别小心一个现象:总耗时下降了,但任务完成率也下降了。那说明你的优化挤压的是准确性,不叫成功,叫走偏。
如果优化后数据没变化,果断回滚,重新提出新的假设,重新测量。这个过程听起来枯燥,但每次循环都会让你对系统的认知更深一层。
5. 常见问题与排查技巧实录
5.1 为什么我测出来的数据波动特别大?
数据波动大的原因最常见的就是环境不纯净。测量期间是否开了其他应用?是否在跑网络上下载?冷却风扇是不是在狂转——这暗示 CPU 被占满了。
还有一个很隐蔽的原因:任务里包含随机性。Claude 这类模型本身就是生成式的,同样的输入,每次输出不完全相同,耗时也有天然波动。应对办法是多测几轮取分布,不要拿单次数据干扰判断。建议 10 轮起步,必要时 20 轮。
另外检查你的测试任务是否真的“可复现”。如果任务里包含“读当前时间”“读取最近文件”这种动态信息,那么每一轮的输入都不一样,测量就没法对齐。
5.2 工具显示一切正常,但实际就是很慢?
这种情况我遇到过好几次。计时器显示某个命令只花了 1 秒,但整轮任务从发出到结束花了 30 秒。问题出在哪?很可能是在工具之外——比如任务调度、进程启动、日志写入、队列等待。
解决办法是扩大测量边界。不要只测工具函数本身,而是测“端到端的完整链路”。从你按下回车开始计时,到最终结果展示出来为止,这段才叫用户感知耗时。然后逐步缩小边界,找出那 29 秒到底藏在哪。
5.3 优化后效果不明显,该怎么办?
先别急着推翻方案。检查你有没有真的改动到位——有时候你只是在思维中觉得改了,实际配置没刷新。刷新配置、重启进程,再测一轮。
如果确实改到位了还是没效果,那就回头仔细审视你的测量数据:当初定位的方向是不是本身就错了?比如你认定是上下文膨胀,但数据里输入 Token 占比只有 10%,那这个方向就是错的。回滚改动,从数据里重新找线索——测量最珍贵的地方就在于它允许你“正确地失败”,而不是在迷雾里瞎撞。
5.4 性能优化问题速查表
| 症状 | 优先检查指标 | 常见根因 | 第一优化动作 |
|---|---|---|---|
| 指令发出后长时间无反应 | 首字延迟、输入 Token 数 | 上下文膨胀/输入过大 | 精简 prompt,拆分任务 |
| 总耗时长、输出特别多 | 输出 Token 数、响应总时长 | 输出无约束、过度生成 | 加输出长度限制、明确格式 |
| 模型不慢,但任务反复重试 | 工具调用次数、重试次数 | 指令不清晰、缺少批量意识 | 指令中明确步骤,合并批量操作 |
| 本地空转、等待时间长 | 网络 RTT、磁盘 IO | 远程等待、IO 开销 | 本地缓存、批量请求,减少取数 |
| 数据不稳定,忽快忽慢 | p50/p95 对比、波动幅度 | 环境噪声、缓存冷热 | 隔离环境、多次采样、看分布 |
写在最后:测量是一种纪律
我自己把“先测量再优化”坚持了半年多,最大的收获不是某个具体项目提速了多少,而是思维方式的转变。现在遇到任何“慢”的问题,我的第一反应不再是“我觉得是……”,而是“先跑一遍看看数据”。这句话说起来简单,做起来需要一点自律:忍住立刻动手的冲动,忍住猜测的欲望,先让数字说话。
最后分享一个小习惯:我每次做测量,都会顺手把原始记录存成一个带日期的文件,文件名就叫perf-baseline-YYYYMMDD.md。里面不写结论,只写场景描述、环境信息、原始数据。等到几个月后项目变复杂了、又出性能问题了,翻出当时的基线,真的能救命。测量一次,记录一次,这就是性能优化最朴素也最实用的纪律。