1. 性能暴涨6倍背后的起点:被长帧打穿的低端机
去年底接手了一个Cocos Creator 3.8的2D卡牌项目,业务迭代倒还行,真正让人头疼的是低端安卓机上的性能反馈。玩家的集中反馈是“进战斗卡成PPT”,尤其是弹窗、飘字和技能特效同时出现时,连点击响应都开始迟钝。我拿性能分析工具在骁龙660级别的最低配测试机上跑了一遍,满帧预算几乎从头到尾被打穿:主线程单帧超过100ms的“长帧”非常密集,帧率在15到20之间来回跳,要是再赶上资源加载,整个战斗场景基本不能玩。
这种局面下,团队第一反应是“再压一下DrawCall”“再删一点资源”,但按老办法手动排查了两天,效果很有限。原因很简单:性能问题从来不是一个原因造成的——渲染批次、纹理内存、shader变体、JS层的序列化热点,全都叠在一起,人工翻Profile找主次优先级,效率实在太低。也是在那个节点上,我们决定引入AI Agent来做一次彻底的问题盘点和优化落地。
这里说的AI Agent,不是页面里聊两句的那种对话机器人。我搭的是一个具备工具调用能力的Agent,给它开放了项目源码的只读访问、构建脚本的执行权限,以及输出目录的写入权限。它还配了一个“项目知识库”目录,里面放的是Cocos官方性能文档、团队编码规范、目标机型分级表。第一件事,我先写了个十几行的脚本,把Profiler导出的原始日志压成“按函数耗时排行的Top50”和“按资源大小排行的Top30”两张表,再丢给Agent做分析。
1.1 症状:一进战斗,帧预算被打穿
先说现象。优化前的战斗场景里,DrawCall峰值经常拉到110左右;打开资源管理器看内存,纹理常驻动不动就400MB以上;再配合动画、粒子特效、实时飘字,主线程在每一帧要做的事情非常拥挤。结果就是:平均帧率看着好像还有20帧左右,但体验上远不如20帧,因为长帧、掉帧、卡顿全部集中在这几秒钟里发生。这种“数据看着还行、体验完全不行”的情况,在移动端性能优化里特别典型,纯看平均帧率很容易被骗。真正要盯的数据,是每一帧到底花了多少毫秒、哪些函数的耗时排在前面、哪些资源一直占着内存不释放。
1.2 Agent的第一轮分析:定位优先级比多改代码更重要
Agent拿到我整理的Top榜单之后,没有直接甩一堆优化方案,而是先根据源码和文档把问题重新分了类。它给出的四类优先级是这样:渲染批次数过高、纹理内存过大、shader变体数量异常、JS层存在高频序列化热点。坦白说,前两项团队自己也能猜到,但后两项是它从构建日志和函数耗时里对比出来的,这在之前手动排查时完全没意识到。从那一刻开始,我对Agent的态度从“试试看”变成了“认真当工具用”。这个阶段最大的收获,不是它给了多惊艳的方案,而是它把乱成一团的性能数据梳理成了一张有优先级的清单,让我知道该从哪下手、哪些问题可以往后放。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. AI Agent接入Cocos工作流的三种方式,和我最终选用的那套配置
在真正落地之前,我把市面上几种做法都梳理了一遍。如果只是遇到了一个单点问题,比如某个报错、某个函数很慢,直接把报错信息和Profile片段复制给大模型,让它给建议,这是最轻量的做法,我称之为“对话式”。但它有两个绕不开的短板:上下文窗口有限,没法跨文件理解一个完整项目;它也不具备验证自己判断的手段,说错了你很难及时发现。
第二种是我最终重点采用的“工具型Agent”。我不只让它看一段文本,而是给它暴露了一组工具:读文件、搜代码、执行构建脚本、读取产物日志。它拿到性能数据后,能自己去看源码确认热点到底发生在哪、资源到底在哪些场景里被引用,然后输出带理由的修改建议。这种形态才真正配得上叫Agent,因为它有“感知-决策-行动”的闭环,而不只是一问一答。
第三种是做CI侧的“流水线型Agent”。每次构建完成之后,自动跑一组固定的性能回归场景,把DrawCall数、帧耗时、内存峰值这些指标生成报告,如果某个版本比上一版明显劣化,Agent直接标记异常并通知到群里。这一层主要是“防止性能继续坏下去”,和前两种负责“把现有问题解决掉”不同。
我最终的方案是“工具型Agent为主,流水线型Agent兜底”。工具型Agent负责日常诊断和优化落地,流水线型Agent保证后面不会回退。选这套组合的原因很实际:项目里性能问题不是一次性能清完的,之后每次加需求、换资源、改Shader,都可能把性能打回原形;没有持续回归,优化效果撑不过两个版本。
2.1 给Agent配一个“会查资料的脑子”
工具型Agent要真正好用,不能只是给它权限就完事。我做了三件事:
- 把项目文档和官方性能优化手册放到Agent可检索的目录里,让它给出的建议能对应到Cocos版本的真实API。
- 在提示词里写清楚“只做最小改动,提交前必须附带理由和影响面分析”。
- 把目标机型分级表喂给它,比如哪些机器支持ASTC纹理、哪些只支持ETC2,让资源优化建议不脱离真实设备。
这三件事看起来简单,实际上极大减少了幻觉。最典型的一次,是Agent推荐了ASTC纹理格式,如果没有机型表,它会忽略那一批老设备不支持的问题;有了分级表之后,它自己就会区分“高端机用ASTC、低端机回退ETC2”。这也是我在实操中反复跟团队强调的:想让Agent输出靠谱,关键是让它先有足够靠谱的上下文。具体实现上,我们用的是支持工具调用能力的Agent框架,把几个命令封装成工具暴露给它,它需要看哪个文件、跑哪条构建命令,都是自己决策的,不用我一步步喂。
2.2 新人入门怎么起步
如果团队还没有搭过工具型Agent,最推荐的方式不是一上来就整全套,而是先把“对话式”跑通:导出Profile数据,用脚本清洗出Top榜,把榜单和目标设备信息一起丢给大模型,让它分类并排优先级。这个阶段你会很快体会到“数据给得越规范,输出越靠谱”。等这种流程稳定了,再逐步加工具调用能力和CI回归,避免第一周就陷入基建泥潭。我见过不少团队一上来就搭Agent框架,结果工具链没通、权限乱配,折腾一个月还没产出,反而对AI辅助开发失去了信心。先小步验证价值,再扩大投入,这条路稳妥得多。
3. Agent揪出的四块性能大头:合批、纹理、Shader变体与JS陷阱
这一章是全文的干货区。Agent每查出一个问题,我们的处理路径都是:复现→定位→出方案→验证。每个单项都单独提分支,分支合入后跑一次固定场景压测,确保收益可测量。四个问题不是按难度排序,而是按对低端机体验的影响排序,效果也最直观。
3.1 DrawCall从110降到21:散图回归图集,动态节点单独分层
DrawCall一直是战斗场景的核心痛点。Agent扫描了resources目录下的散图引用,把“被多个预制体使用、但没进图集的纹理”列了一张表,又检查了同材质却被打散的节点,最后给出的思路是:
- 开启自动图集,让UI资源尽量合到同一张大图里。
- 动态频繁变化的节点(飘字、血条、光效)单独放一层,避免因为它们的变动拆散整批合批。
- 相同学材质的粒子优先走实例化合批通道,而不是各自独立提交。
落地后,一个典型战斗界面的DrawCall从110降到了21。表面上看是省了80多次提交,真正起作用的是主线程CPU用在渲染提交上的时间大幅释放出来了。渲染每个批次都要经过CPU提交、GPU状态切换。批次数越低,主线程的固定开销越少,留给逻辑和动画的帧预算就越多。这步做完之后,战斗场景的掉帧曲线已经平滑了一大截。特别提醒一点:如果动态节点混在静态图集里,哪怕只有一个节点在变,整批合批都可能被打破,所以“分层”比“强行合批”更重要。
3.2 纹理压缩与内存释放:从400MB峰值降到190MB
DrawCall解决的是“主线程忙不忙”,内存解决的是“会不会挂”。优化前,项目里的UI图、背景图大量使用RGBA8888,很多大图在场景切换之后仍然留在内存里。Agent从构建产物里扫出一张“内存占用榜”,前几张背景大图加一起占掉了超过150MB,这放在低端机上非常致命。
我们做了三件事:
- 在构建时配置纹理压缩,UI资源压到ETC2/ASTC,按机型分级下发。
- 不必要开mipmap的全部关掉,避免内存近翻倍。
- 场景切换时统一清理不再使用的动态资源,并检查是否还有强引用把资源拖住;高频创建销毁的节点(弹窗、飘字、特效)放入对象池。
优化后,峰值内存从420MB左右降到了约190MB,低端机的闪退问题基本消失。在没有改变任何玩法逻辑的前提下,内存压力直接减半,战斗场景在后台切回时也不再频繁重建。这里有个容易被忽略的点:资源清理不是单纯调用释放接口就完了,还得确认代码里没有强引用把资源“钉”住,不然释放函数执行了、内存却一点没降,排查起来非常隐蔽。
3.3 最容易忽略的隐性杀手:Shader变体爆炸
这个问题的发现纯属意外。Agent在看构建日志时发现,shader编译耗时异常的高。顺着日志往源码里翻,定位到了一个被大量复制使用的Label渐变shader。团队里为了实现渐变效果,先后复制了多个版本,每个版本又因为使用了不同的特性宏组合,导致构建时生成出来的shader变体数量远超预期。
在Cocos这类引擎里,shader变体是编译时根据宏组合展开的,宏一多、复制一份,组合数量很容易翻好几倍。后果就是首包启动时编译耗时暴增,运行时的渲染状态切换也变慢。更隐蔽的是,这个问题不会在帧率曲线里显眼地跳出来,但它一直在消耗启动性能和GPU状态切换时间。
处理方式三步走:
- 把渐变效果从宏开关改成uniform参数,同一份shader覆盖所有用法,不再让每个节点生成独立组合。
- 把该shader放到公共资源库,禁止业务侧随意复制修改。
- 在首页场景预编译常用shader,避免进战斗时才现场编译。
这一项做完,构建产物里的shader变体数量从三千多掉到八百多,首包编译耗时明显下降,进入战斗场景的等待时间也接近减半。如果只看DrawCall和包体,这个问题很难被发现,所以Agent的构建日志分析在这里起了大作用。
3.4 JS层性能陷阱:高频JSON.stringify才是隐藏卡顿源
最后一个大头来自JS引擎层。联机战斗的战斗状态每次发生变化,代码都会把整个状态对象做一次JSON.stringify再发送到后台;同时还有一些上报逻辑也会顺手对整个对象做序列化。Agent在Profiler的函数耗时榜单里发现,JSON.stringify长时间排在前几位,并且内存曲线在频繁序列化时段出现明显的锯齿形抖动——这是典型的GC压力。
你如果单独看一段代码,很难想象JSON.stringify能吃掉多少时间,但放到一秒钟执行几十次、每次序列化一个几百KB对象的场景里,问题会立刻放大。我们的处理方式是:
- 联机同步改成增量协议,只发送变化字段,而不是每次全量序列化整个对象。
- 上报数据改成轻量二进制格式,减少字符串创建和内存分配。
- 高频日志加上频率限制,允许异步批量上报,不再阻塞主线程。
改动之后,这个函数的耗时从Top榜单前列彻底消失,战斗过程中的帧率抖动明显减少。对一个实时性要求比较高的游戏客户端来说,这一项的收益甚至比一些渲染优化更直接。我当时特意让Agent在改动前先扫描了全部“JSON.stringify”的调用点,避免只改了一个函数、漏掉另一个同样高发的序列化热点。
4. “6倍”是怎么测出来的:同一台设备上的完整性能对照
4.1 为什么我拿“帧耗时”算倍数,而不是“帧率”
很多人看到“性能暴涨6倍”第一反应是“帧率翻6倍”,这其实是一个常见的理解偏差。帧率(FPS)是每秒渲染多少帧,帧耗时(ms)是渲染一帧花了多少时间,二者是倒数关系,但不是线性的。优化前平均帧率15FPS,对应帧耗时约66ms;优化后60FPS,对应帧耗时约16ms。帧耗时下降了约4倍,如果拿平均帧率算又是另一种数字。所以一开始就得定好口径,否则对外汇报时很容易被挑战。
我们实际取的基准是最差情况下的主线程单帧总耗时:优化前,低端机在激烈战斗场景里经常出现单帧超过120ms的长帧;优化完成后,同样路径下的峰值帧耗时压到了20ms以内。120ms到20ms,这才是“6倍”这个数字的来源。帧率曲线在优化前是15到20帧来回跳,优化后能维持在55到60帧,但严格说,这次收益用“长帧耗时下降6倍”来表述最准确,不会有争议。
4.2 优化前后的完整数据对照
下面是我们固定在同一台低端安卓机、同一条操作路径、同一个版本分支上跑出来的结果:
| 指标 | 优化前 | 优化后 | 变化 |
|---|---|---|---|
| 主线程每帧总耗时(峰值口径) | 约120ms | 约20ms | 降至约1/6 |
| 平均帧率(复杂战斗) | 15~20 FPS | 55~60 FPS | 明显提升 |
| 长帧率(单帧超100ms)占比 | 约8% | 0.2% | 下降约40倍 |
| 内存峰值 | 约420MB | 约190MB | 降幅约55% |
| 启动到进入战斗场景 | 约8秒 | 约4.5秒 | 接近减半 |
需要说清楚的是,并不是每个场景都能达到6倍。列表简单、逻辑少的场景,优化前本来就不卡,优化后也看不出翻天覆地的变化。真正收益巨大的是那些渲染密集、动态节点多、实时同步频繁的重战场合;如果你的项目没有这种压力场景,6倍只是一个参考,不是用来炫耀的数字。
4.3 每次只改一个变量,收益才能归因
这次优化能留下可信数据,很大程度上归功于一种有点“笨”但有效的纪律:每次只改一个变量。比如这次只调整图集和合批,下一次只压纹理,再下一次只改序列化逻辑。每个改动独立分支,合并后立刻跑同一条压测路径。
这样做的好处很直接:如果某个指标变了,你能明确知道是哪个改动引起的。之前团队习惯把一堆优化一起发版,最后出了问题根本不知道是哪个环节导致的,这次完全避开了。另外,我们还在CI脚本里做了一张自动化性能报告,构建完自动压测、自动出报告,Agent会对比前后两个版本的指标并在劣化时发出告警。后来有两次DrawCall回退,都是这个回归Agent先把它揪出来的,比人工发现快了很多。这套报告机制也成了团队通晒性能数据的固定载体,谁改了什么、效果怎么样,一眼就能看清楚。
5. 踩坑实录:Agent给方案但不会背锅,兜底机制怎么搭
AI Agent不是万能的,这一点在整个优化过程中被验证了好几次。它在“找规律、读文档、批量改代码”上确实强,但对“业务后果”的理解经常缺根弦。所以我们的原则从一开始就没有变过:Agent可以动手,但代码评审、兼容性判断、灰度验证,必须由人来做。
5.1 UI合批的坑:RenderRoot开过头,层级乱了
Agent在分析时找到一个官方文档里的合批优化方向,建议给界面根节点开启渲染根优化。它“好心”地给多个界面都加上了配置,结果合并之后,弹窗被背景盖住,部分UI的显示层级完全错乱。原因是合批不是越合越好,它要求同材质、同纹理、同层级顺序;不同的UI根节点如果存在跨层遮挡关系,强行合并反而会破坏遮挡关系。最后我们把相关提交整体回退,只保留了纯静态列表场景里的合批改动。
这个坑给我的教训很直观:Agent能读懂文档里的“可以开”,但读不到你项目里某个弹窗的z序需求,也判断不了哪些节点之间存在遮挡关系。这类上下文必须靠人来补,或者在提示词阶段就提前写清楚约束,比如“只对纯静态列表开启合批,动态弹窗、跨层盖压的界面一律不动”。说白了,指令表达得越贴近业务现实,Agent闯祸的概率就越低。
5.2 动画帧事件的坑:缓存优化误伤业务刷新
第二次踩坑是在动画优化环节。团队想把战斗中飘字的运行开销降下来,Agent发现动画帧事件里有一段“每次取值”的逻辑比较重,就擅自改成缓存。结果飘字动画本身播放正常,但和该事件绑定的伤害数值刷新却不再触发了。这又是一个典型的“性能角度没问题、业务角度出事”的案例。
修复本身不复杂,回退提交,把伤害数值刷新逻辑恢复到原样。但这件事给我们提了个醒:动画帧事件往往和玩法数据绑定在一起,表面看是性能热点,实际上是业务触发链路上的一环,直接在回调上做缓存,极有可能改变事件触发时机。从那以后,我们对动画事件相关文件加了一条明确规则:Agent只能做扫描和报告,不能直接修改回调逻辑;真要优化,先把事件链路图画出来,由业务负责人确认之后再动。这种“先报告、后动手”的流程,在涉及业务语义的模块里非常必要。
5.3 纹理压缩的坑:ASTC低端机花屏
纹理压缩这块也踩了一次。Agent推荐直接用ASTC,理由是压缩率高、画质损失小。但项目里的低端机列表中,有一批老设备不支持ASTC,按Agent的方案全量替换之后,测试组在低端机上看到了花屏。后来我们把纹理格式改成按机型分级下发:高端机用ASTC,低端机回退ETC2。
这个案例进一步印证了前面说的:设备兼容性表格不能只是放在知识库里,而是要在提示词里被标记为“硬性约束”。Agent优化时要先查目标机型表再选纹理格式,不能用默认推荐。后来我们把纹理格式检查直接写进CI流程,构建产物里一旦出现目标机型不支持的纹理格式,构建直接失败,从机制上杜绝了这类问题再犯。这也算是“被坑过一次之后,用流程把坑填上”的典型操作。
5.4 兜底机制:四道关卡保证Agent改动可回退
经历了上面几个坑,我们把兜底机制固定成了四道:
- 强制代码Review:所有Agent生成的改动走PR流程,不允许直接推到主干。
- 性能回归用例:构建后自动跑固定压测场景,超过阈值自动失败。
- 小步独立分支:每个优化主题单独一个分支,遇到问题能快速回退,不影响其他进度。
- 灰度发布:优化合入后先给内部体验包和少量外部用户,确认无异常再全量。
这套机制搭好之后,Agent的效率优势才真正发挥出来:它批量产出的优化代码有人审、有测试、能回滚,团队不会再因为“AI改了不知道哪里错了”而不敢用。说直白点,用Agent不是把方向盘交给它,而是给它配了一个“能快速撤销的加速踏板”——它负责踩油门,人负责看路和踩刹车。
6. 把Agent辅助优化固化进流程后,我最想留下的几条建议
6.1 先建基线,再谈优化
整个项目最开始的失误,就是“感觉卡”就去调,没有一个客观基线。后来我们把下面这些固定下来,每次都先跑一套再做任何改动:目标机型、测试场景、操作路径、指标口径(DrawCall、帧耗时、内存峰值、启动时间)。没有基线就谈不上优化,更谈不上“涨了几倍”。基线也不复杂,就是把固定场景跑一遍,把数据存起来,之后每次改动完毕再跑一遍,对比差异即可。这件事花不了多少时间,但对后续判断收益、排查回退都非常关键。
6.2 适合交给Agent的活,和不适合的活
结合这次经验,我给出一个我觉得比较靠谱的分工:
- 适合Agent:整理Profile热点并归类、扫描大纹理和散图、搜索高频序列化点、批量给重复创建节点套对象池、统一shader参数、在CI里对比性能报告。
- 不太适合Agent:需要玩家体验和业务上下文来做判断的交互改动、涉及线上灰度策略的调整、以及所有带“不可逆后果”的错误处理逻辑。
核心判断标准就一条:这件事能不能靠“检索、统计、模式匹配”帮助完成,如果能,Agent大概率干得比你快;如果必须依赖业务判断和后果评估,先让人看清楚再动手。我们后来给Agent加了一张“高危文件列表”,涉及玩法核心逻辑、数值结算、动画帧事件的模块默认只读,Agent提交的改动里一旦触碰这些文件,PR系统会自动标红并要求人工二次确认。这条规则一加,后面几乎没再出过“好心办坏事”的改动。
6.3 一点个人体会
性能优化做了这么多年,我最深的感受是:最耗人的不是写优化代码,而是定位“到底卡在哪”。手动翻Profile、猜逻辑、试配置,一个长帧问题经常要花掉半天一天。AI Agent真正改变的不是“替代了谁”,而是把定位效率拉高了一个量级,让我把时间花到更值得做的取舍上去。它先给我一张有优先级的问题清单,我再根据业务情况决定先改哪个、用哪种方案,这种配合方式比我自己从零开始翻数据舒服太多。以后我们组的每个性能优化任务,默认都会配一个Agent来做第一步扫描和分类,这个流程我应该是不会倒回去了。
