Claude Code 或 Codex 写出的代码,即使通过单元测试,也可能让游戏变慢。重构或新增功能可以正常运行,但帧时间、GPU 时间和内存占用未必和之前一样。CI 不仅要确认“能不能运行”,还要确认“有没有比上一个构建更慢”。
测试通过,也不代表性能没有下降
写代码的工夫一降下来,瓶颈就从「写」挪到了「确认」。功能能不能跑,测试会自己给出答案。可这段代码有没有把真机帧时间悄悄抬高几毫秒,一片绿的测试是不会告诉你的。
改动越多,逐个用手去剖析就越不现实。渲染线程上不知不觉多了一次分配,更新循环的计算量悄悄变差,GC 触发得更频繁。这些都能通过单元测试,也躲得过肉眼的代码评审,直到放上真机才作为体感上的劣化显现出来。
功能测试确认“能不能运行”,构建比较确认“有没有比之前更慢”。
性能比改动前更差,称为性能回归。与其只靠评审者的警觉或老手的直觉来发现回归,不如让每个构建都接受同一套标准的检查。
让 CI 自动发现性能下降
要做的事很简单。给每个构建收集性能遥测,和上一个已知良好的构建相比,一旦性能下降超过阈值,就让 CI 失败。这样无需每次都由人盯着数字,也能在合并前发现性能下降。
Framedash 的 framedash perf-diff 会给出两个构建在帧时间、内存、GPU 时间上的 P50 和 P95。加上 --fail-on-regression,当候选构建的 P50 超过阈值变差时,它会返回退出码 1,让 CI 失败(判定采用 P50 比较,P95 作为参考一并列出)。
framedash perf-diff --baseline "$BASE_SHA" --candidate "$GITHUB_SHA" \
--threshold 5 --fail-on-regression
比较的对象除了帧时间、内存、GPU 时间,还包括地图加载时间(load_time_ms)和磁盘 IO(io.*),都按越小越好的指标处理。你可以用 --metric 只比较某个指标,或者用 --map 和 --platform 限定比较范围。
这里的关键是作为比较单位的 build_id。如果一个数字属于哪个构建都定不下来,构建之间的比较本身就无从谈起。
工作流全貌
下面来看智能体写出的代码在合并前如何接受性能检查。
性能下降超过阈值时让 CI 失败,再进入原因调查。
在 CI 里把这条流程一把跑完的是 framedash run-profile-test。这条命令会写出自动会话用的 FRAMEDASH_* 环境变量,启动你指定的剖析构建,等它的遥测被收集进来,再用 perf-diff 与基线比较。
framedash run-profile-test \
--command "./Build/Game.exe -nullrhi -ExecCmds='Automation RunTest Perf'" \
--scenario nightly --api-key-file ci-read.key \
--baseline "$BASE_SHA" --threshold 5 --fail-on-regression
被启动的游戏这一侧,在自动测试入口处调用一次自动会话 API。当 SDK 调用 BeginAutomatedSessionFromEnvironment(),它会读取 run-profile-test 写出的 FRAMEDASH_BUILD_ID / FRAMEDASH_GIT_BRANCH / FRAMEDASH_GIT_COMMIT / FRAMEDASH_TEST_SCENARIO,此后每个事件都自动带上 CI 构建以及它的分支、提交、场景。不需要逐个事件打标签的代码。
TelemetrySDK.Instance.BeginAutomatedSessionFromEnvironment();
// ... 运行剖析场景 ...
TelemetrySDK.Instance.EndAutomatedSession();
这里有一个运维上的注意点:短构建或无头运行可能在 SDK 的定期刷新之前就退出。请让运行一直存活到遥测真正发出为止(各 SDK 的 CI 与无头运行指南里有做法);随后 run-profile-test 会等遥测被收集进来,再比较性能。
build_id 记为顶层字段,分支、提交、场景则挂在 ci.branch / ci.commit / ci.scenario 属性上。这样,智能体提交的 build_id 就直接成了构建比较的候选。
只有密钥的区分要留意。CI 的比较过程用 analytics:read 密钥读取遥测,被启动的游戏则用另一把 events:write 采集密钥发送遥测。为避免名字冲突,比较用的密钥通过 --api-key-file 传入,而不是使用 FRAMEDASH_API_KEY 环境变量,让 FRAMEDASH_API_KEY 留给游戏当采集密钥。
CI 检出性能下降后,该看哪里
perf-diff 让 CI 失败时,你能知道的只到哪个指标变差了、差了多少。在哪里变差,光凭这个数字看不出来。
这时候用的是性能热力图。它把 FPS、帧时间、GPU 时间、内存占用按格子叠加在你的游戏地图上。你可以按设备或构建配置筛选,于是能把性能变差的构建里哪张地图哪一带变重了,定位成地图上的一个位置。
地图上的分布,是由带位置的事件画出来的。当你的剖析场景把玩家位置连同一个已注册的地图 ID 一起上报,SDK 就会给这些事件自动附上相机朝向(偏航和俯仰)。有了这些,「build 1042 的 desert_ruins,朝北看的一个角落里 P95 会跳」这种粒度你就能追下去。性能下降就此成了可复现的调查对象。
用遥测帮助智能体调查原因
上面这条流程里还剩一处有人:打开热力图、缩小原因、写下修复代码。
这道工序也能交给智能体。Framedash 的 MCP 服务器把针对遥测的只读工具和资源开放给 LLM。热力图网格数据、仪表盘 KPI,乃至原始 SQL 查询,智能体都能从一句自然语言指令直接拉出来。
claude mcp add framedash \
-e FRAMEDASH_API_KEY=fd_xxx \
-e FRAMEDASH_PROJECT_ID=your-project-uuid \
-- npx -y @framedash/mcp-server
这样就能让负责这次改动的智能体继续调查和修复。用 get_heatmap 俯瞰地图上变重的地方,用按 build_id 过滤的 query 把候选构建的热点单独摘出来,形成对原因的判断,然后改代码。工具是只读的,所以智能体不会改写或删除你的遥测。修复有没有消除性能下降,会由下一个构建中的同一项 CI 性能比较再次确认。
CI 根据数字检出性能下降,智能体调查可能的原因。人负责设定阈值,并在最后确认修复方式是否妥当。
如何开始
你需要做的,只是给游戏接入 SDK、从 CI 传入 build_id,再往流水线里加一项性能比较。SDK 接入几行就够,支持的引擎是 Unity、UE5、Godot (C#)。
claude plugin marketplace add crane-valley/framedash-claude-plugin
claude plugin install framedash@framedash