You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
203 lines
11 KiB
203 lines
11 KiB
|
3 weeks ago
|
# 性能分析:抖音小游戏线上发热 / 卡顿 / 崩溃
|
||
|
|
|
||
|
|
> 日期: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: false`
|
||
|
|
- `enableStressFrameRate: 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)
|
||
|
|
|
||
|
|
1. **同屏硬顶**:按 `DeviceCapability.quality` 设默认 `maxConcurrent`(建议 low 40 / medium 60 / high 80,含双口合计)。超限只推迟出怪,不改配表只数。
|
||
|
|
2. **表现同步每渲染帧最多 1 次**:固定步长多步时仅最后一步调 `onCombatTick`;阴影/LOD 再降频。
|
||
|
|
3. **抖音默认关 `enableBattleResRetain`**(或仅 high + 未收到过 MEM-WARN)。战斗相位告警允许 `releaseOwner('battleRetain')`。
|
||
|
|
4. **COMBAT 停止 `startBattleSilentDownload`**;Boot `kickLoad` 战斗包改为「点开始后」或更低并发。
|
||
|
|
5. **iOS**:`enableIOSHighPerformanceModePlus` 仅高档或关掉 Plus;验证发热。
|
||
|
|
|
||
|
|
### 随后(P1)
|
||
|
|
|
||
|
|
6. low/medium 战斗目标帧 45(内存 D2 再 30);**大厅可保持 60**。这与当前「战斗不降帧」产品策略冲突,需要明确授权。
|
||
|
|
7. 接上 `monsterSortInterval`;AI 决策降频。
|
||
|
|
8. 节点池 cap + 长局 trim。
|
||
|
|
9. 拆武器合图、压 1667 背景、Spine/背景挂压缩预设。
|
||
|
|
10. 评估战斗是否必须每怪刚体(逻辑命中已存在则关物理)。
|
||
|
|
|
||
|
|
### 回归(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` |
|