11 KiB
性能分析:抖音小游戏线上发热 / 卡顿 / 崩溃
日期:2026-09-17
范围:静态代码 + 资源 + 构建配置审计(无线上 PF 原始日志、无真机 Profiler)
渠道:sdkConfig.config.chanId = tt(DefaultConfig / 悟空别走)
方法:全平台内存方案七大支柱 × 全面性能工程五维(加载 / 内存 / FPS / CPU / 发热)
一句话:中后期双口叠波没有同屏上限,战斗又全程 60fps 并用固定步长追赶表现同步,再叠加进战资源峰值与退战保留战斗包,低中端抖音机会同时出现发热、掉帧、OOM 杀进程。
1. 观测结论(当前能确定什么)
本次没有线上 PF-MEM-WARN / PF-THRM-FPS 原始样本,不能把「人均告警次数」写成数字。能确定的是机制本身足以在真机上同时打出三类症状:
| 症状 | 主因类型 | 峰值还是基线 |
|---|---|---|
| 崩溃(杀进程 / 白屏 / 闪退) | GPU 纹理 + JS 节点/Spine 峰值;同屏怪无上限 | 峰值为主,长局再叠 基线 |
| 卡顿 | 固定步长追赶 × 全量 onCombatTick;2 倍速再乘 |
战斗中持续,掉帧后恶化 |
| 发热 | 战斗 60fps 不降、iOS 高性能模式、常亮、Box2D、Spine | 战斗相位持续 |
建议立刻用现有打点核对(见 docs/性能打点说明.md):
PF-BOOT-TIER:抖音大盘m*/q*分布(大量 7.3–8.5 分会落 medium / tier1)PF-MEM-WARN:是否集中ph3(战斗)PF-SESS-END:退战后b/p/a/r是否回落PF-THRM-FPS:战斗是否几乎全是fps60
2. 阶段图与峰值叠加点
Boot 最小包 (first/comm/home + BootRes)
│ kickLoad 战斗五包 zip(不可 abort)
▼
Hall 可交互
│ kickAllRemainingBundleZips + silent 大厅 UI
│ 匹配页 scheduleBattleWarm → loadResPakg(BattleRes)
▼
进战 ★峰值1
│ 大厅包未卸干净 + 战斗 zip 解压 + loadResPakg + 关面板
│ PhysicsSystem2D.enable = true
▼
COMBAT ★峰值2(随波次单调变差)
│ 双口出怪、波次重叠、无 maxConcurrent
│ 60fps + 30Hz 逻辑最多追 5 步/渲染帧
│ startBattleSilentDownload(popBattle / loadDir)
▼
退战
│ tier1/3:battleRetain 钉住五包 zip,大厅仍扛战斗包 ★基线抬升
▼
下一局进战 ★峰值3(大厅 + 保留战斗包 + 新 load)
3. 七大因素对照
| 因素 | 现状 | 对策 | 平台影响 |
|---|---|---|---|
| 纹理/Spine/音频 | 52 张 max 边 >1024;剑/棍/枪合图近 2k;地图背景 1100×1667;构建有 ASTC 8×8,Spine/背景未挂 per-asset 压缩 | 拆武器 atlas、压背景、真机确认 GFX 格式 | 抖音低内存机 GPU OOM |
| 加载/预载 | Boot 即并行 kickLoad 战斗包;开战后再 startBattleSilentDownload |
战斗 COMBAT 禁 silent;kickLoad 错峰到点击开始 | 进战瞬间 IO+解压发热 |
| 释放/生命周期 | 退战顺序正确;tier1/3 enableBattleResRetain=true;战斗中告警不能卸 retain |
抖音默认关 retain,直到有 B/P/A 证据 | 大厅基线高,二次进战峰值 |
| 引擎引用/UI | 关面板 destroy 成对;节点池 无上限 | 池加 cap,长局 trim | 长局 JS+节点膨胀 |
| JS 堆 | 热路径 sort / new 数组;world.gc 每 60 逻辑帧全扫 |
表现同步降频;有界 cache | 掉帧后 GC 尖峰 |
| 逻辑/表现热路径 | 见 §4,P0 | 表现 1 次/渲染帧;同屏 cap | 发热+卡顿主因 |
| 平台/容器 | iOS High Performance Mode Plus 打开;战斗 setKeepScreenOn(true);deviceScore≥7.30 当中端 |
iOS 高性能仅高档;中端按低内存策略 | 抖音 iOS 发热、Android 杀后台 |
4. P0 根因(必须先做)
4.1 双口叠波 + 无同屏上限(崩溃 / 卡顿 / 发热共同主因)
| 项 | 证据 |
|---|---|
| 配表 | Level.json 第 18–25 波每波 50 只,间隔 1.6s |
| 双口 | LevelWaveParser.toWaveDefs 每波 port1(攻 AI)+ port2(攻玩家)各一份 → 100 只/波 |
| 叠波 | BattleRuntime 创建会话 waveClearRequiresKillAll: false;Const 10 = 1,8(后续波 8 秒就开下一波) |
| 上限 | WaveSpawner._canSpawnMore:maxConcurrent 缺省 0 = 不限;Level 表无该字段 |
一波 50 只 × 1.6s ≈ 80s 才出完;8s 开下一波。清不掉时理论上可叠约 10 波 × 100 ≈ 1000 只同屏(杀怪会压下来,但中后期 2 倍速仍可到百级)。每只怪带 Spine + 运动同步 + Kinematic RigidBody2D。
根因:对抗玩法把出怪乘 2,又允许波次重叠,却没有按档位的同屏硬顶。
4.2 固定步长追赶把表现同步乘最多 5 倍(卡顿正反馈 / 发热)
- 逻辑
fixedStep = 1/30,maxFixedIterations = 5(BattleSession) - 每一步 COMBAT 都调
CocosBattleMovementBridge.onCombatTick:遍历全部怪物节点syncMonsterNode+ 全阴影 + Spine LOD - 掉到 20fps 时,一帧最多跑 5 次逻辑 + 5 次全量表现同步 → 更掉帧 → 更追赶
tickSliceCount 只切 ECS system,不切 bridge。2 倍速 TimeScale=2 再把追赶窗口加宽。
根因:表现桥接绑在 _tickOnce 上,而不是「每渲染帧 1 次」。
4.3 战斗相位强制 60fps,温控故意不降帧(发热)
thermal-profile.json.ts 三档都是 battleFrameRate: 60,且:
enablePhaseFrameRate: falseenableStressFrameRate: false
ThermalGovernor 注释写明:禁止用降帧换发热。内存告警只停 silent。抖音中端(deviceScore 7.30–8.50)仍满帧 + 飘字 24 + 受击 16 + 关 Spine LOD 间隔 30。
叠加:
build-templates/bytedance-mini-game/game.json:enableIOSHighPerformanceMode+ Plus 均为 true- 战斗
AppLifecycle.setBattleKeepScreen(true)→ 无法靠熄屏降温
根因:流畅策略覆盖了低中端机的热预算;iOS 高性能模式把 GPU 顶满。
4.4 进战 / 退战内存峰值(崩溃)
| 叠加 | 位置 |
|---|---|
Boot 完成后并行 kickLoad 战斗分包(不可 abort) |
BootPipeline → PreloadPredictor.startEarlyBattleBundleDownload |
开战后再 startBattleSilentDownload |
GameController 打开 BattleGame 成功后 |
中端/高端退战 enableBattleResRetain=true 钉五包 zip |
memory-profile.json.ts tier1/3;GameController._enterMainReleaseBattleAndLoadHall |
战斗中 onMemoryWarning 不能卸 retain |
MemoryDowngrade._releaseRetainedBattleRes:phase===Battle 直接 return |
| 节点池无 cap | ResourcesManager.putNode |
抖音 ≥7.30 分 = medium = tier1 = 开 retain。这是抖音大盘主力机,不是「高端才开」。
5. P1(放大 P0,或单独造成中度问题)
| ID | 问题 | 位置 | 贡献 |
|---|---|---|---|
| P1-1 | DeviceProfile.monsterSortInterval 未接线,Y 排序仍事件触发全量 |
DeviceProfile vs CocosBattleMonsterPresenter.sortMonstersByY |
CPU |
| P1-2 | 主线固定 mode: 'pvp',双玩家槽 + AiBattleBridge 每帧 tick |
GameController / UIBattleGame.update |
CPU/内存近翻倍 |
| P1-3 | BattleRuntime.tick 串行十余个 Host(雷公/哪吒/箭雨/长矛/被动/弓/AI…) |
BattleRuntime.ts 783+ |
CPU |
| P1-4 | 每怪 Kinematic RigidBody2D,进战开 PhysicsSystem2D |
Monster.ts / BattlePhysicsGate |
CPU/发热 |
| P1-5 | 武器皮肤+技能 多皮肤一张 atlas(剑 1961、约 3.18MB 源图) | assets/res/battleGameWeapon/spine/like/... |
GPU |
| P1-6 | 地图/大厅全屏背景 1100×1667,超 Tier-0 单图 1024 | 花果山背景.png 等 |
GPU |
| P1-7 | MemoryProfile.reapplyAfterProbe 全仓库无调用;DeviceProfile._tier 首次访问后锁死 |
MemoryProfile.ts / DeviceProfile.ts |
档位可能偏乐观 |
| P1-8 | TargetResolver 威胁排序比较里反复 estimatePathLength(含 sqrt) |
TargetResolver.ts |
CPU 尖峰 |
| P1-9 | enableConsolePfLog = true(Release 虽 noop console.log,开发包/部分宿主仍有 I/O) |
sdkConfig.ts |
次要 |
6. 五维影响(若不改)
| 加载 | 内存 | FPS | CPU | 发热 |
|---|---|---|---|---|
| 首局被战斗 zip 并行下载托住 | 进战峰值 = 大厅+战斗+silent;tier1 退战基线不回落 | 掉帧后追赶使帧时更长 | 同屏实体 × 追赶次数 × Host 链 | 60fps + iOS Plus + 常亮 + Box2D |
7. 建议治理顺序(策划数值不变)
不改出怪数量、间隔、奖励。只加表现/内存硬顶与生命周期。
立刻(P0,可分 PR)
- 同屏硬顶:按
DeviceCapability.quality设默认maxConcurrent(建议 low 40 / medium 60 / high 80,含双口合计)。超限只推迟出怪,不改配表只数。 - 表现同步每渲染帧最多 1 次:固定步长多步时仅最后一步调
onCombatTick;阴影/LOD 再降频。 - 抖音默认关
enableBattleResRetain(或仅 high + 未收到过 MEM-WARN)。战斗相位告警允许releaseOwner('battleRetain')。 - COMBAT 停止
startBattleSilentDownload;BootkickLoad战斗包改为「点开始后」或更低并发。 - iOS:
enableIOSHighPerformanceModePlus仅高档或关掉 Plus;验证发热。
随后(P1)
- low/medium 战斗目标帧 45(内存 D2 再 30);大厅可保持 60。这与当前「战斗不降帧」产品策略冲突,需要明确授权。
- 接上
monsterSortInterval;AI 决策降频。 - 节点池 cap + 长局 trim。
- 拆武器合图、压 1667 背景、Spine/背景挂压缩预设。
- 评估战斗是否必须每怪刚体(逻辑命中已存在则关物理)。
回归(Release + 抖音真机,不可用编辑器代替)
- 清缓存冷启动 → 立刻开战 / 大厅停 10s 再战
- 战斗 ↔ 大厅 ×10:无缺图、B/P/A 不单调涨
- 打到第 18 波以后:同屏怪数、帧时、
tt.onMemoryWarning - 2 倍速 15 分钟:发热主观 + 是否杀进程
- 机型:deviceScore <7.3、7.3–8.5、≥8.51 各至少 1 台;iOS 开/关高性能模式对比
- 弱网进战、切后台 5s
8. 未决(需策划 / 发行 / 真机)
- 同屏 cap 是否允许「推迟出怪」改变手感(数量不变,节奏变)
- 战斗降到 45fps 是否可接受
- 线上
PF-MEM-WARN是否集中 ph3、是否与 retain 机型重合 - 抖音包实际 ASTC 上传率(开发者工具会强制 PNG,不能当证据)
9. 相关代码入口
| 模块 | 路径 |
|---|---|
| 波次双口 | assets/script/battle/config/LevelWaveParser.ts |
| 无并发上限 | assets/mx/module/battle/unit/WaveSpawner.ts |
| 固定步长 + bridge | assets/mx/module/battle/core/BattleSession.ts、CocosBattleMovementBridge.ts |
| 60fps 策略 | assets/script/game/thermal-profile.json.ts、ThermalGovernor.ts |
| 内存档 / retain | assets/script/game/memory-profile.json.ts、GameController.ts |
| 抖音分档 | assets/script/game/device-capability-standards.ts |
| 降档不卸战斗 | assets/script/game/MemoryDowngrade.ts |
| iOS 高性能 | build-templates/bytedance-mini-game/game.json |