返回全部文章

用 CI 自动发现 AI 智能体所写游戏代码的性能下降

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 失败,再进入原因调查。

在 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 性能比较检出用同一阈值确认是否比基线更差
热力图与 MCP调查由智能体调查性能在哪里变差

CI 根据数字检出性能下降,智能体调查可能的原因。人负责设定阈值,并在最后确认修复方式是否妥当。

如何开始

你需要做的,只是给游戏接入 SDK、从 CI 传入 build_id,再往流水线里加一项性能比较。SDK 接入几行就够,支持的引擎是 Unity、UE5、Godot (C#)。

  • SDK 和 CI 的具体配置整理在 CI 集成剖析里。
  • 命令清单请看 CLI 参考
  • 要交给智能体使用的话,Claude Code 插件会一次性装好 MCP 服务器和技能。
claude plugin marketplace add crane-valley/framedash-claude-plugin
claude plugin install framedash@framedash
代码生成再快,也不要把性能检查落在后面 支持的引擎是 Unity、UE5、Godot (C#)。无需信用卡即可开始。
免费开始 UnityUE5Godot (C#)