主题切换
性能分析工具实操
预计阅读 20 分钟
版本信息
最后更新:2026-07-09 · 适用基础库:≥ 3.9.0(最低兼容 ≥ 2.30.0) · 微信开发者工具:稳定版 1.06+
性能优化的前提是测量。本章手把手带你使用微信小游戏的主流性能分析工具,从开发者工具到真机测试全覆盖。
工具全景
| 工具 | 适用阶段 | 分析维度 | 是否需要真机 |
|---|---|---|---|
| 微信开发者工具 Performance 面板 | 开发期 | FPS/CPU/内存/渲染 | 不需要 |
| 微信开发者工具 Audits 面板 | 提审前 | 综合评分 + 优化建议 | 不需要 |
| Cocos Creator Profiler | 开发期 | Draw Call / 节点数 / 纹理内存 | 不需要 |
| PerfDog | 测试期 | FPS/CPU/GPU/内存/网络/功耗 | 必须真机 |
| WeTest 云测 | 测试期 | 多机型兼容性 + 性能 | 云真机 |
微信开发者工具 Performance 面板
录制步骤
- 打开微信开发者工具 → 选择小游戏项目。
- 点击「调试器」→「Performance」标签。
- 点击 ● 录制按钮 → 在模拟器中操作游戏 → 点击 ■ 停止。
- 分析火焰图(Flame Chart)中的函数调用耗时。
重点关注的指标
┌────────────────────────────────────────────┐
│ FPS (绿色条) │ 目标: ≥ 55 │
│ CPU (蓝色区域) │ 关注长任务 (Long Task) │
│ Memory (紫色线) │ 关注持续上涨趋势 │
│ Network │ 关注大文件/并发请求 │
└────────────────────────────────────────────┘常见性能瓶颈定位
typescript
// 使用 Performance API 在代码中手动埋点
class GamePerformance {
private marks: Map<string, number> = new Map()
/** 标记开始 */
start(label: string): void {
this.marks.set(label, Date.now())
}
/** 标记结束并输出耗时 */
end(label: string): number {
const start = this.marks.get(label)
if (!start) return 0
const elapsed = Date.now() - start
console.log(`[Perf] ${label}: ${elapsed}ms`)
this.marks.delete(label)
return elapsed
}
/** 获取当前 FPS(基于 requestAnimationFrame 的滑动窗口估算) */
private static frameTimestamps: number[] = []
static tickFPS(): number {
const now = Date.now()
this.frameTimestamps.push(now)
// 只保留最近 1 秒的帧记录
while (this.frameTimestamps.length > 0 && this.frameTimestamps[0] < now - 1000) {
this.frameTimestamps.shift()
}
return this.frameTimestamps.length
}
/** 获取当前内存占用 (MB),真机数据以 PerfDog 为准 */
static getMemoryUsage(): number {
const perf = wx.getPerformance?.()
if (perf?.memory) {
return perf.memory.usedJSHeapSize / 1024 / 1024
}
// 微信小游戏环境通常不支持 performance.memory,返回 -1 表示不可用
return -1
}
}Cocos Creator Profiler
开启调试面板
typescript
// 任意脚本中
import { profiler } from 'cc'
// 显示调试信息:Draw Call / 顶点数 / 三角面 / 纹理内存
profiler.showStats()
// 或在编辑器中:预览窗底部工具栏 → 点击「调试信息」切换显示关键指标
| 指标 | 健康值 | 说明 |
|---|---|---|
| Draw Call | ≤ 100 | 超过 100 会影响帧率 |
| Tris (三角面) | ≤ 50K | 2D 游戏通常远低于此值 |
| Texture Memory | ≤ 50MB | 纹理总占用量 |
| Node Count | ≤ 500 | 活跃节点数 |
| Frame Time | ≤ 16.6ms | 单帧处理时间(60fps 下) |
PerfDog 真机性能测试
PerfDog 是微信官方推荐的移动端性能测试工具,免费使用。
测试流程
- 下载并安装 PerfDog(Windows/macOS)。
- 手机 USB 连接电脑 → 开启 USB 调试模式。
- PerfDog 中选择微信 → 小游戏进程。
- 启动游戏 → 录制性能数据 5-10 分钟。
- 导出报告 → 分析各指标趋势。
关键指标解读
FPS ████████████████████░░ 均值 58 / 最大值 60 / 低帧率占比 2%
CPU Usage ██████████░░░░░░░░░░░░ 均值 15% / 峰值 35%
Memory (PSS) ██████████░░░░░░░░░░░░ 均值 180MB / 峰值 220MB
GPU Usage ██████░░░░░░░░░░░░░░░░ 均值 8% / 峰值 20%
Temperature ████░░░░░░░░░░░░░░░░░░ 均值 35°C / 峰值 38°CPerfDog 测试建议
- 覆盖场景:至少测试「首页 → 核心玩法 → 结算 → 返回」一个完整循环。
- 长时间挂机:测试 30 分钟,观察内存是否持续上涨(内存泄漏)。
- 多机型覆盖:至少 1 台千元安卓(如 Redmi)+ 1 台 iPhone(如 iPhone 8)。
- 网络模拟:在弱网(3G)下测试 CDN 资源加载速度。
性能分析检查清单
在提审或正式发布前,逐项检查:
| 检查项 | 方法 | 通过标准 |
|---|---|---|
| 帧率 | PerfDog / DevTools FPS | 核心玩法 ≥ 55fps |
| 内存 | PerfDog Memory | 峰值 ≤ 300MB, 无持续上涨 |
| 启动时间 | wx.reportPerformance / 手动计时 | 首帧 ≤ 3s |
| 包体 | 构建产物大小 | 主包 ≤ 4MB |
| Draw Call | Cocos Profiler / GPU 抓帧 | ≤ 100 |
| 音频延迟 | 手动测试 | 点击到音效响起 ≤ 100ms |
| 弱网表现 | Charles 限速 / 开发者工具 | 3G 下 60s 内可进入游戏 |
性能调试的常见误区
避坑指南
- 只在开发者工具中测试 — 模拟器性能远优于真机,结论不可靠。
- 只测高端机 — 你的用户可能有 30-50% 使用千元机。
- 只看平均值 — 峰值才是决定体验的关键,FPS 的 P95/P99 比均值更有意义。
- 忽略 GC 暂停 — 偶尔的 GC 会导致 100-500ms 的卡顿,需关注对象创建/销毁模式。
📚 相关阅读
- 08 性能优化与包体控制 — 针对性能分析发现的瓶颈进行优化
- iOS 高性能模式 — iOS 高端机型的特殊优化模式
- 异常监控与日志 — 线上性能数据的持续监控
📝 课后练习
练习 1:使用 PerfDog 录制并分析游戏性能数据
题目: 按照以下步骤使用 PerfDog 完成一次完整的性能测试,并撰写分析报告:
- 连接一台安卓真机(建议千元机,如 Redmi 系列),安装 PerfDog
- 选择「微信 → 你的小游戏进程」开始录制
- 按以下流程操作游戏并记录:
- 启动 → 等待首页渲染完成(记录首帧时间)
- 进入核心玩法 → 连续游玩 5 分钟(关注 FPS 稳定性)
- 故意触发高负载场景(如大量粒子特效、频繁碰撞检测)
- 切后台 30 秒 → 再切回前台(观察内存恢复情况)
- 正常结束录制
- 导出 PerfDog 报告,关键分析:
- FPS 均值 / P95 / 低帧率占比
- 内存峰值 / 是否有持续上涨(判断内存泄漏)
- CPU 占用峰值场景
- 切后台前后内存差异
参考答案:
分析报告应包含以下结构:
markdown
## 性能测试报告 — [游戏名] v[版本号]
### 测试环境
- 设备:Redmi Note 12 (8GB RAM, Android 14)
- 微信版本:8.0.x
- 录制时长:8 分钟
### 测试结果
| 指标 | 均值 | 峰值 | P95 | 评估 |
| --------- | ----- | ----- | --- | ------------------------ |
| FPS | 58.2 | 60 | 55 | ✅ 达标 |
| 内存(PSS) | 180MB | 245MB | — | ⚠️ 峰值接近 300MB 警戒线 |
| CPU | 12% | 35% | 25% | ✅ 正常 |
| 温度 | 36°C | 39°C | — | ✅ 正常 |
### 问题发现
1. 内存从 165MB 持续上涨至 245MB,可能存在纹理/对象未释放
2. 切后台后内存仅回收 10MB,期望回收更多
### 优化建议
1. 检查粒子特效的生命周期管理
2. 场景切换时强制调用 `wx.triggerGC()`练习 2:手动埋点统计帧时间与 P99 计算
题目: 在游戏代码中实现一个帧时间统计器,完成以下功能:
- 在每帧开始和结束时记录时间戳
- 维护最近 300 帧(约 5 秒)的帧时间滑动窗口
- 每 60 帧输出一次统计报告(均值、P95、P99、最大值)
- 当 P99 帧时间超过 20ms 时输出警告
- 考虑使用循环缓冲区优化内存
参考答案:
typescript
// FrameTimeMonitor.ts
class FrameTimeMonitor {
private readonly BUFFER_SIZE = 300
private frameTimes: Float64Array // 使用 TypedArray 减少内存开销
private writeIndex = 0
private frameCount = 0
private frameStart = 0
constructor() {
this.frameTimes = new Float64Array(this.BUFFER_SIZE)
}
/** 在每帧开始时调用 */
beginFrame(): void {
this.frameStart = performance.now()
}
/** 在每帧结束时调用 */
endFrame(): void {
const elapsed = performance.now() - this.frameStart
this.frameTimes[this.writeIndex] = elapsed
this.writeIndex = (this.writeIndex + 1) % this.BUFFER_SIZE
this.frameCount++
// 每 60 帧输出一次统计
if (this.frameCount % 60 === 0) {
this.report()
}
}
private report(): void {
const samples = this.getRecentSamples()
samples.sort((a, b) => a - b)
const avg = samples.reduce((s, v) => s + v, 0) / samples.length
const p95Idx = Math.floor(samples.length * 0.95)
const p99Idx = Math.floor(samples.length * 0.99)
const p95 = samples[p95Idx]
const p99 = samples[p99Idx]
const max = samples[samples.length - 1]
console.log(
`[FrameTime] avg: ${avg.toFixed(1)}ms | ` +
`P95: ${p95.toFixed(1)}ms | P99: ${p99.toFixed(1)}ms | ` +
`max: ${max.toFixed(1)}ms | samples: ${samples.length}`
)
// P99 超过 20ms 警告(60fps 下正常为 ~16.67ms)
if (p99 > 20) {
console.warn(`⚠️ P99 帧时间 ${p99.toFixed(1)}ms 超过 20ms 警戒线,` + `可能存在性能瓶颈`)
}
}
/** 获取最近的采样数据(排除未填充的缓存区位置) */
private getRecentSamples(): number[] {
const count = Math.min(this.frameCount, this.BUFFER_SIZE)
const samples: number[] = []
for (let i = 0; i < count; i++) {
const idx = (this.writeIndex - 1 - i + this.BUFFER_SIZE) % this.BUFFER_SIZE
samples.push(this.frameTimes[idx])
}
return samples
}
}
// 在游戏循环中使用
const monitor = new FrameTimeMonitor()
function gameLoop() {
monitor.beginFrame()
// ... 游戏逻辑更新 ...
monitor.endFrame()
requestAnimationFrame(gameLoop)
}关键设计点:
- 使用
Float64Array替代普通数组,避免 GC 压力 - 循环缓冲区避免数组扩容开销
performance.now()精度达到微秒级,适合帧级别测量