Skip to content

Stage05|Performance ​

Lesson072|阶段复习:建立可复现、可量化的性能证据链 ​

Tags: #Creator2.x #Stage05 #Performance #Profiler #OptimizationDifficulty: ⭐⭐⭐⭐☆


阶段挑战:把“感觉卡”变成可证伪报告 ​

性能工程不是收集优化口诀,而是把用户症状转换成:

text
可复现动作
→ 时间线与指标
→ 瓶颈假设
→ 单变量实验
→ 修改与回归
→ 目标设备结论

本课用一页“活动商城”验收 Stage05 的全部能力。

一、本课目标 ​

串联 Lesson055~Lesson071,完成 Performance 阶段闭环。

本阶段的核心问题是:

面对“为什么卡”,如何从现象建立一条能复现、能测量、能验证的证据链?


二、性能知识树 ​

text
四个维度
├── CPU
├── GPU
├── Memory
└── IO

分析工具
├── Frame Time / FPS
├── Profiler
├── 内存和资源统计
└── 真机平台工具

常见系统
├── Node / Component
├── Layout / ScrollView
├── Label / Animation / Particle
├── GC / ObjectPool
└── Load / Release / Cache

三、性能排查标准流程 ​

text
稳定复现
    ↓
记录设备、版本、数据量和操作
    ↓
测量 Frame Time、CPU、GPU、Memory、IO
    ↓
判断持续超预算还是偶发尖峰
    ↓
找到最大成本和触发条件
    ↓
提出单一假设
    ↓
修改一个变量
    ↓
回归测试和记录指标

四、从 API 到性能问题 ​

修改 UI ​

text
label.string
→ 文本和 RenderData 更新
→ Layout 传播
→ DrawCall / Texture
→ CPU / GPU 成本

创建特效 ​

text
instantiate
→ 生命周期和调度
→ Particle / Sprite
→ GC、DrawCall、Overdraw
→ destroy 或对象池回收

打开场景 ​

text
loadScene
→ IO / 解码 / 反序列化
→ Node / Component 创建
→ 首次布局和渲染
→ 内存峰值

性能分析的价值就是沿着这些链路找到实际成本。


五、阶段综合练习 ​

问题:

text
一个活动页面首屏打开慢,滑动时偶尔卡顿,重复进入后内存持续升高。

请按以下格式提交分析:

text
现象:
复现步骤:
设备和版本:
指标:
初步维度判断:
可能根因:
单变量实验:
优化结果:
残余风险:

参考思路:

text
打开慢 → 资源加载、解码、实例化、首屏布局
滑动卡 → ScrollView、Layout、Label、Overdraw
内存升高 → 事件、异步回调、缓存、Texture、对象池

六、Stage05 面试题 ​

1. FPS 和 Frame Time 的区别是什么? ​

FPS 是每秒帧数,Frame Time 是完成一帧的时间。性能排查通常更关心帧时间分布和尖峰。

2. DrawCall 少为什么仍可能卡? ​

Overdraw、Shader、纹理带宽、分辨率或 CPU 其他工作仍可能超预算。

3. destroy 节点为什么不等于释放资源? ​

资源可能被 SpriteFrame、其他节点、缓存、Bundle 或 GPU 继续引用。

4. 对象池的风险是什么? ​

状态重置遗漏、事件和 Tween 残留、池无限增长以及长期内存占用。

5. 如何排查内存泄漏? ​

重复进入和退出场景,观察趋势,检查事件、异步回调、全局引用、缓存、资源和对象池。

6. 为什么优化前后要单变量验证? ​

否则无法判断哪个修改造成收益或回归。


七、本阶段最终能力 ​

完成 Stage05 后,你应该能够:

  • 用 Frame Time 而不是只用 FPS 描述卡顿。
  • 先判断 CPU、GPU、Memory、IO 维度。
  • 使用 Profiler 选择长帧和稳定区间。
  • 解释 Node、Layout、Label、Particle、GC 和对象池成本。
  • 设计加载、缓存、释放和内存峰值方案。
  • 写出可复现、可量化、可回归的性能报告。

最终记住:

高级优化不是“知道更多优化技巧”,而是能用证据证明问题、修改和结果之间的因果关系。


阶段综合实验:Performance Evidence Lab ​

场景、资源、Profiler 标记和测试数据通过 Creator 2.4.x 工作流建立。不修改生成目录或序列化资源文本。

负载开关 ​

准备以下可独立启用的模块:

text
NodeLoad:2000 节点/组件
LayoutLoad:500 条自适应 Item
LabelLoad:高频动态文字
PoolLoad:弹幕/伤害数字
LoadPeak:大图 + Prefab 加载
GpuLoad:全屏透明层/粒子
LeakLoad:故意保留引用(仅实验)

EvidenceLab.ts ​

ts
const { ccclass, property } = cc._decorator;

@ccclass
export default class EvidenceLab extends cc.Component {
    @property([cc.Node])
    modules: cc.Node[] = [];

    private samples: number[] = [];
    private begin = 0;

    startRun() {
        this.begin = Date.now();
        this.samples.length = 0;
        cc.log('[evidence] begin');
    }

    mark(name: string) {
        cc.log(
            '[evidence]',
            name,
            Date.now() - this.begin
        );
    }

    enableOnly(index: number) {
        for (let i = 0; i < this.modules.length; i++) {
            this.modules[i].active = i === index;
        }
    }

    update(dt: number) {
        this.samples.push(dt * 1000);
        if (this.samples.length > 600) {
            this.samples.shift();
        }
    }

    report() {
        const values = this.samples.slice().sort((a, b) => a - b);
        if (!values.length) {
            return;
        }
        const at = (ratio: number) =>
            values[Math.floor((values.length - 1) * ratio)];
        cc.log(
            '[evidence]',
            'p50=', at(0.5),
            'p95=', at(0.95),
            'p99=', at(0.99),
            'max=', values[values.length - 1]
        );
    }
}

验收流程 ​

  1. 固定设备、构建、数据和动作。
  2. 只启用一个模块,确认指标变化。
  3. 组合 Node/Label/Layout,记录 CPU 与 p99。
  4. 组合 Texture/LoadPeak/Gpu,记录 IO、内存和 GPU。
  5. 运行 LeakLoad 十次循环,确认故意增长能被证据发现。
  6. 对每个改动做回归和失败路径测试。

性能报告模板 ​

text
症状:
用户动作:
设备/构建:
数据规模:
基线:
主要指标:
假设:
实验变量:
结果:
修改:
副作用:
回归矩阵:
结论与未解决风险:

没有“未解决风险”的报告通常意味着没有诚实记录边界。

Stage05 故障树 ​

text
首屏慢
├── IO/解码/上传
├── 一次性 instantiate
├── Label/Layout/字形
├── Shader/纹理预热
└── 并发预加载峰值

持续低帧
├── CPU update/遍历/布局
├── GC/高频分配
├── DrawCall/状态提交
├── GPU Overdraw/FillRate
└── 分辨率/热降频

越进越慢
├── 监听/定时器闭包
├── 缓存无淘汰
├── 对象池无上限
├── Texture/Asset 引用
└── 场景循环残留

先按症状选择证据,再按证据选修复。

阶段真实案例:性能修复互相抵消 ​

团队同时:

  • 把资源移入 Bundle;
  • 增大对象池;
  • 开 Dynamic Atlas;
  • 关闭部分 Label;
  • 加后台预加载。

最终首屏变快但内存和滚动变差,无法知道哪项造成副作用。

正确流程是拆分提交或保留 feature flag,逐项测:

text
基线
→ A 资源拆分
→ B 对象池
→ C 动态合图
→ D Label 降频
→ E 后台预加载
→ 组合方案

组合方案仍需重新测,因为非线性相互作用可能出现。

Stage05 能力验收 ​

你应该能够:

  1. 区分 CPU、GPU、Memory、IO 症状与证据。
  2. 用 Frame Time 分位数描述长帧。
  3. 在 Creator Profiler 和平台工具中建立可复现采样。
  4. 计算图片/Atlas 的内存数量级并设计预算。
  5. 解释 Node、Layout、Label、GC、Pool 的不同成本。
  6. 设计 load/preload 的首屏与峰值策略。
  7. 为监听器、缓存、池和资源写所有权/淘汰点。
  8. 在微信小游戏目标上做包体、内存、渲染和网络回归。
  9. 产出带基线、假设、实验和副作用的性能报告。

综合练习与答案 ​

练习一 ​

页面平均 58 FPS,p99 75 ms;关闭全屏半透明层后 p99 22 ms,CPU几乎不变。结论是什么?

答案: 有强 GPU/Overdraw 证据;需继续测覆盖面积、Shader/RenderTexture,并在目标设备确认,不要只凭一次开关结论。

练习二 ​

场景循环后 JS Heap 稳定但 Texture Memory 每轮上涨。优先查什么?

答案: 动态 Texture/Atlas 的缓存、addRef/decRef、SpriteFrame/对象池和随机缓存键;JS 堆正常不能排除原生/GPU引用。

练习三 ​

对象池把创建尖峰降为零,但内存超预算。方案是什么?

答案: 记录并发峰值,缩小池容量/分层池化/动态缩容,简化 Item,清理资源引用;不能回到无限预热。

练习四 ​

后台预加载让首屏偶尔长帧,如何验证?

答案: 在相同冷缓存设备关闭后台任务做 A/B,标记 IO/解码/上传和 p99;限制并发或延后到首屏稳定后,比较首屏、全完成、峰值和滚动。

版本边界与验证入口 ​

Stage05 的数值必须绑定设备、构建和平台;Creator 2.4.x 内置工具与 Web/Native/小游戏平台工具不可混为同一口径。

八、下一阶段预告 ​

Stage05 完成,下一阶段进入:

text
Stage06|JSB & Native Bridge
Lesson073|JavaScript Engine、JSB 与 Cocos2d-x 的关系

下一阶段会把性能和运行时继续向下追踪:

text
JavaScript
→ JavaScript Engine
→ JSB
→ C++ Engine
→ Native Platform