让 Agent 先调查代码为什么慢,再提出重写要求
本文目录
开展性能调查时,先给出可重复的工作负载、你关心的开销,以及记录测量结果的位置。改变实现之前,请 Agent 分清一个看似合理的解释,与实际观察到的瓶颈。
本教程使用原创虚构的 Python 任务和一份空白调查记录。命令面向 Python 3.14,供你按需调整;它们没有为本文实际执行,也没有产生实测的速度比较。
固定工作负载与预期结果
假设一个小程序按标签汇总记录的大小。目前的实现,会针对每个不同标签把全部记录扫描一次:
# slow_case.py — educational Python 3.14 example
def totals_by_tag(records):
tags = sorted({record["tag"] for record in records})
return {
tag: sum(record["size"] for record in records if record["tag"] == tag)
for tag in tags
}
def build_records(count=4000, groups=40):
if count < 0 or groups < 1:
raise ValueError("count must be non-negative and groups must be positive")
return [
{"tag": f"tag-{i % groups:02d}", "size": (i % 5) + 1}
for i in range(count)
]
if __name__ == "__main__":
data = build_records()
totals_by_tag(data)
独立的教学文件与上面的代码一致。本例输入是字典组成的列表,标签为字符串,大小为非负整数。结果中的标签按排序后的顺序插入,且不得改变输入记录。文件加载、网络服务和界面渲染不属于这次函数层面的调查。
在较大工作负载旁边,保留一个小型参考用例:
输入:
[{tag: red, size: 4}, {tag: blue, size: 5}, {tag: red, size: 7}]
预期汇总:
blue: 5
red: 11
预期输出顺序:
先 blue,后 red
这些是根据虚构输入算出的预期值,不是保存下来的测试运行结果。
把“慢”变成一个具体问题
复制下面这张调查卡:
操作及准确代码版本:
Python 实现/版本:
机器及相关环境:
工作负载:
- 输入怎样构造:
- 记录数量与不同标签数量:
- 准备工作是否计入:
- 预期结果及顺序:
要调查的开销:
[整项操作/单个函数/分配开销/其他明确的开销。]
当前实际观察到的测量值:
[没有就留空,不要用估计替代。]
候选解释:
区分这些解释需要的证据:
调查后允许作出的变更:
输入规模大,本身不能说明问题在哪里。记录数量和标签数量都应保留:从实现结构看,它会对每个标签重复扫描记录;但这种结构在你的环境中影响有多大,需要测量才能回答。
区分性能剖析与计时
Python 3.14 的性能剖析文档区分了执行剖析与基准测量。用剖析调查执行过程的开销分布;比较操作耗时时,另设一致的计时方式。
对这个教学文件,可以参考下面的剖析命令:
python3.14 -m cProfile -s cumulative slow_case.py
它会剖析整个脚本,包括工作负载的构造。阅读函数级条目时要记住这个范围。较高的累计时间包含被调用函数的工作,不能把它再与每个子调用的时间相加,得到一个新的总耗时。
若想重复计时,并把输入准备放在被计时语句之外,Python 3.14 的 timeit 接口支持设置准备代码:
python3.14 -m timeit -s 'from slow_case import build_records, totals_by_tag; data = build_records()' 'totals_by_tag(data)'
只在使用预定解释器、并持有教学文件副本时运行这些命令。如果你的运行时是另一个 Python 版本,就检查对应版本的文档,不要把会随版本变化的 /3/ 文档当成相同的 API 约定。
保留一棵假设树
观察到的现象:操作看起来很慢
|
+-- 输入准备占了主要开销?
| 检查脚本剖析;如有必要,单独测量准备过程。
|
+-- 重复扫描占了主要开销?
| 分别改变记录数量和不同标签数量。
| 选择汇总方式的变更前,先检查函数/调用证据。
|
+-- 开销其实在应用的其他部分?
测量真正相关的边界:加载、函数工作和渲染。
不要从这个孤立函数推断整个应用的行为。
这些分支都是候选解释。不要因为代码看起来效率低,就排除其他可能性;应依据对用户真正重要的操作来取得证据。
只提出一项变更,并保持比较公平
如果测量指向重复扫描,一种候选方案是单次遍历汇总大小,随后生成有序输出。保留输入约定、汇总结果、输出顺序和不修改输入的行为。不要把新缓存、并发层和存储格式一起塞进一次难以追溯的重写中。
使用相同输入数据和解释器比较,保持准备过程的边界一致,保留每次计时结果,并说明条件有何变化。剖析报告可以帮助选择方向,但不能因此宣称端到端提速。
| 工作负载 | 版本 | 计时边界 | 实际计时记录 | 是否核对正确汇总/顺序? |
|---|---|---|---|---|
| 4,000条记录;40个标签 | 基线 | 仅 totals_by_tag(data) |
— | — |
| 相同数据 | 候选版本 | 相同边界 | — | — |
| 4,000条记录;4个标签 | 分别记录基线/候选版本 | 相同边界 | — | — |
| 400条记录;40个标签 | 分别记录基线/候选版本 | 相同边界 | — | — |
表中没有填写结果,因为本文没有运行基准测量。空记录清楚保留了下一步:收集基线,或者先返回一份调查计划。
复制给 Agent 的请求
使用所提供的 Python 3.14 示例,调查 totals_by_tag。
先确认输入假设,以及预期的汇总结果和顺序。
区分输入构造与函数自身的开销。
用性能剖析调查候选瓶颈;如果随后要比较基线与候选版本,
记录一致的计时边界。
报告证据之前,不要重写架构。
如果证据支持重复扫描是原因,就提出一项汇总方式的变更,
保留汇总结果、有序输出和不修改输入的行为。
返回工作负载、环境、实际观察、剩余假设,以及一个下一步。
没有运行测量时,给出计划并让结果字段保持空白,
不要报告估计出来的提升。
一份有用的调查,可以止于明确下一次该测什么。当证据支持作出变更时,让修改和比较都足够小,才能说清楚改善了什么、在什么条件下改善,以及哪些行为保持不变。