外观
Stage05|Performance
Lesson066|load、preload、缓存与加载峰值
Tags: #Creator2.x #Stage05 #Load #Preload #Cache #MemoryDifficulty: ⭐⭐⭐⭐☆
真实问题:预加载以后,打开页面为什么仍然内存峰值
项目预加载了 Prefab 和图集,打开页面时仍发生峰值:
text
缓存中的压缩/Asset 数据
+ instantiate 产生 Node/Component
+ Texture 首次上传
+ Label 字形
+ Layout 与 RenderData
→ 打开峰值preload 只能提前部分加载工作,不会自动完成页面构建和渲染预热。
一、本课目标
资源加载优化不是单纯“提前全部加载”。本课区分:
- 正式加载和预加载。
- 缓存命中和首次加载。
- 加载时间、首屏时间和内存峰值。
- 预加载过多为什么也会拖慢启动。
二、load 和 preload 的语义
在 Creator 2.4.x Asset Manager 中,两者不是同一条流程的两个名字:
text
preload:提前下载资源及必要依赖,不做完整反序列化和初始化
load:在业务需要时继续解析、初始化并返回可用 Assetpreload 的目标主要是把网络或文件读取等待前移。它会消耗网络、IO 和下载缓存空间,但不能据此断言 Prefab 对象、解码纹理或 GPU 资源已经常驻内存。后续仍需调用 load。
官方版本依据:Asset Manager|Loading and Preloading。
三、资源加载的成本链路
text
preload 路径:
定位资源 → 读取或下载 → 写入下载缓存
后续 load 路径:
读取下载结果 → 解析 / 解码 → 反序列化
→ 创建运行时 Asset → 在实际使用阶段准备渲染资源不同资源的成本分布不同。图片可能卡在解码和首次 GPU 使用,Prefab 可能卡在反序列化和后续实例化,远程资源还多了网络等待。测量时必须标记当前观察的是 preload、load、instantiate 还是首次渲染。
四、缓存命中为什么更快
text
第一次请求
→ 读取、解析、创建
→ 放入缓存
第二次请求
→ 找到缓存对象或依赖
→ 减少重复 IO 和解析缓存提高速度,但也会延长资源生命周期。性能优化必须同时观察加载时间和内存占用。
五、加载峰值
场景切换时可能出现:
text
旧场景资源仍在
新场景资源开始加载
临时解码数据存在
新纹理等待上传这段重叠时间就是峰值风险。可以采用:
- 分批加载。
- 控制并发。
- 提前释放确定不用的旧资源。
- 先加载首屏必须资源。
- 其余内容延迟到页面稳定后加载。
六、首屏时间和总加载时间
用户关心的是:
text
点击后多久看到可操作内容?不是所有资源都必须在首屏前加载。可以分层:
text
首屏必需
→ 首屏可交互
→ 首屏后增强
→ 用户触发时再加载把所有资源放入启动阶段,会增加首屏时间和峰值。
七、加载竞态
text
用户进入页面 A
→ 开始加载 A
→ 用户快速进入页面 B
→ A 加载完成回调晚到如果没有请求令牌,A 的回调可能修改 B 页面。处理方法:
- 保存请求 ID。
- 页面退出时取消或标记失效。
- 回调检查当前页面和对象有效性。
- 只让当前请求提交结果。
八、案例:商城分层加载
text
进入商城
→ 加载背景、导航和首屏 Item
→ 页面可交互
→ 预加载下一页图标
→ 用户滚动时补充详情资源这样比一次加载所有商品图片更容易控制首屏和峰值。
九、常见误区
误区一:preload 越多越好
预加载会提前占用网络、IO 和下载缓存,并可能和其他下载任务争抢并发;完整解析和运行时资源内存通常仍留在后续 load 阶段。
误区二:缓存命中就没有成本
仍可能有引用、校验、对象创建和 GPU 使用成本。
误区三:加载完成回调一定对应当前页面
异步竞态会让旧请求晚于页面切换返回。
误区四:只看加载时间,不看峰值
压低等待时间但提高内存峰值,可能在低端设备上更危险。
十、练习与答案
- preload 解决什么问题,又带来什么代价?
- 为什么首屏资源要分层?
- 如何防止旧页面加载回调修改新页面?
- 场景切换时为什么容易出现内存峰值?
答案:
- 主要把下载等待前移;代价是提前消耗网络、IO、并发额度和下载缓存,后续
load成本仍需测量。 - 让用户先可操作,再延迟非关键资源。
- 使用请求令牌、生命周期检查或取消机制。
- 新旧资源和临时解码数据在一段时间内重叠。
加载峰值来自“新旧同时存在”
场景 A 切 B:
text
A 的 Node/Texture 尚未释放
→ B 开始读取
→ B 解码缓冲
→ B Texture 上传
→ B instantiate
→ 峰值
→ A 最终归还总加载量不变,改变先后顺序就能显著改变峰值,但可能影响转场体验。
Creator 2.4.x 实验:分阶段打开页面
LoadPeakProbe.ts
ts
const { ccclass, property } = cc._decorator;
@ccclass
export default class LoadPeakProbe extends cc.Component {
@property(cc.Node)
container: cc.Node = null;
private generation = 0;
private prefab: cc.Prefab = null;
preload() {
cc.resources.preload(
'perf-lab/heavy-panel',
cc.Prefab,
(err) => cc.log('[load-peak] preload', !!err)
);
}
load() {
const id = ++this.generation;
const begin = Date.now();
cc.resources.load(
'perf-lab/heavy-panel',
cc.Prefab,
(err, prefab: cc.Prefab) => {
cc.log('[load-peak] load ms=', Date.now() - begin);
if (err || id !== this.generation) {
return;
}
this.prefab = prefab;
this.buildAcrossFrames(id, 0);
}
);
}
private buildAcrossFrames(id: number, count: number) {
if (id !== this.generation || !this.prefab) {
return;
}
for (let i = 0; i < 5; i++) {
this.container.addChild(cc.instantiate(this.prefab));
}
if (count < 20) {
this.scheduleOnce(
() => this.buildAcrossFrames(id, count + 1),
0
);
}
}
cancel() {
this.generation++;
}
}对照
- 不 preload,直接 load+一次创建 100 个。
- preload 后再 load+一次创建。
- preload 后分帧创建。
- 每种记录 load callback、首屏可用、全部完成、p99 与内存峰值。
分帧可降低单帧尖峰,但总完成时间可能更长,应定义首屏优先级。
缓存命中不是免费
即使 Asset 已缓存,仍可能有:
- instantiate;
- Component 生命周期;
- 数据绑定;
- Layout;
- GPU 首次使用;
- 大量对象一次性加入树;
- JS→Native 提交。
优化报告应拆开:
text
resource ready
first usable UI
all content ready真实项目故障:商城预加载拖慢战斗
为了商城秒开,游戏启动后立即后台预加载全部商城图集。玩家直接进入战斗时,商城 IO/解码与战斗资源争用,造成战斗开场卡顿和内存峰值。
修复:
- 按用户路径与优先级调度;
- 战斗关键加载完成后再后台;
- 限制并发和单帧任务;
- 可取消/降优先级的预加载;
- 内存不足时不保留低价值缓存;
- 测“预加载对当前页面”的干扰。
版本边界与练习答案
Creator 2.4.x preload/load 的具体准备边界需按 Asset 类型和平台验证,不能假设 preload 已完成 GPU 上传。
- preload 与 load 的目标差异是什么? preload 提前准备依赖/数据,load 正式取得 Asset 供业务使用;具体边界按版本。
- 分帧创建的代价是什么? 总完成时间可能变长,状态管理更复杂,需提供渐进 UI。
- 缓存越多越好吗? 命中更快但内存更高,并可能干扰当前关键路径。
- 如何验收加载优化? 同时看冷/热路径、首屏可用时间、全部完成、p99、峰值内存和取消正确性。
十一、本课总结
text
加载优化要同时看等待时间、首屏时间、缓存命中和内存峰值。
预加载应服务于体验,而不是把所有资源提前搬进内存。十二、下一课预告
text
Lesson067|资源释放、场景切换与内存不下降