|
| 1 | +# 最佳实践检测工具 |
| 2 | +- [最佳实践检测工具](#最佳实践检测工具) |
| 3 | + - [作用](#作用) |
| 4 | + - [如何使用](#如何使用) |
| 5 | + - [检测示意](#检测示意) |
| 6 | + - [检测指标解释](#检测指标解释) |
| 7 | + - [启动检测](#启动检测) |
| 8 | + - [监控指标](#监控指标) |
| 9 | + - [优化建议概览](#优化建议概览) |
| 10 | + - [预下载检测](#预下载检测) |
| 11 | + - [监控指标](#监控指标-1) |
| 12 | + - [优化建议概览](#优化建议概览-1) |
| 13 | + - [wasm分包检测](#wasm分包检测) |
| 14 | + - [监控指标](#监控指标-2) |
| 15 | + - [优化建议概览](#优化建议概览-2) |
| 16 | + - [网络信息检测](#网络信息检测) |
| 17 | + - [监控指标](#监控指标-3) |
| 18 | + - [优化建议概览](#优化建议概览-3) |
| 19 | + - [帧率检测](#帧率检测) |
| 20 | + - [监控指标](#监控指标-4) |
| 21 | + - [优化建议概览](#优化建议概览-4) |
| 22 | + |
| 23 | + |
| 24 | +## 作用 |
| 25 | +为了使游戏达到比较好的性能表现,仍需要开发者结合游戏实际情况进行优化。平台针对启动和运行输出了大量优化手段,使用最佳实践检测,可帮助开发者在**开发阶段**针对问题进行优化 |
| 26 | + |
| 27 | +## 如何使用 |
| 28 | +版本要求:`转换插件版本 > 202305230000` |
| 29 | + |
| 30 | +导出小游戏默认在**开发版和体验版**开启检测,可通过`minigame/unity-namespace.js`修改 |
| 31 | + |
| 32 | +```js |
| 33 | +// 最佳实践检测配置 |
| 34 | +unityNamespace.monitorConfig = { |
| 35 | + enableMonitor: true, // 是否开启检测(只影响开发版/体验版,线上版本不会检测) |
| 36 | + fps: 10, // 帧率低于此值的帧会被记录,用于分析长耗时帧,做了限帧的游戏应该适当调低 |
| 37 | + showResultAfterLaunch: true, // 是否一直检测到游戏可交互完成 |
| 38 | + monitorDuration: 30000, // 仅当showResultAfterLaunch=false时有效, 在引擎初始化完成(即callmain)后多长时间停止检测 |
| 39 | +} |
| 40 | +``` |
| 41 | + |
| 42 | +默认检测条件如上。插件并不知道什么时候检测截止,可选择可交互上报后或在引擎初始化完成(`callmain`)后多少ms截止,根据游戏实际情况修改。 |
| 43 | + |
| 44 | +1. 有上报游戏可交互[`WX.ReportGameStart()`](ReportStartupStat.md#三上报自定义阶段)的游戏 |
| 45 | +应该设置`showResultAfterLaunch=true`,同时会忽略`monitorDuration`的值 |
| 46 | + |
| 47 | +1. 未上报游戏可交互上报的游戏 |
| 48 | +应设置`showResultAfterLaunch=false`,此时根据`monitorDuration`的值截止检测 |
| 49 | + |
| 50 | +## 检测示意 |
| 51 | + |
| 52 | +优化建议通过弹框提示,详细内容通过vconsole打印 |
| 53 | + |
| 54 | +1. 弹框提醒优化建议 |
| 55 | + |
| 56 | +<img src="../image/monitor/monitor-dialog.png" width="500"/> |
| 57 | + |
| 58 | +2. console打印详细信息 |
| 59 | + |
| 60 | +<img src="../image/monitor/monitor-console.png" width="500"/> |
| 61 | + |
| 62 | +## 检测指标解释 |
| 63 | + |
| 64 | +### 启动检测 |
| 65 | +检查框架启动阶段的耗时和资源大小 |
| 66 | + |
| 67 | +#### 监控指标 |
| 68 | +``` |
| 69 | +{ |
| 70 | + assetLoadCost: number; // 首资源包下载耗时,单位ms |
| 71 | + assetContentLength: number; // 首资源包大小(未压缩原始大小),单位bytes |
| 72 | + useContentEncoding: boolean; // 首资源包是否开启服务器压缩 |
| 73 | + wasmLoadCost: number; // wasm包下载耗时,ms |
| 74 | + wasmContentLength: number; // 启动下载的wasm包大小,单位bytes |
| 75 | + useCodeSplit: boolean; // 是否使用了wasm代码分包 |
| 76 | + callmainCost: number; // 引擎初始化耗时,ms |
| 77 | +} |
| 78 | +``` |
| 79 | + |
| 80 | +#### 优化建议概览 |
| 81 | +当提示优化建议时,可采用对应的优化手段 |
| 82 | +1. `未使用wasm代码分包` |
| 83 | +- 条件: `useCodeSplit`为`false`,未使用wasm代码分包 |
| 84 | +- 优化手段: [使用代码分包工具](WasmSplit.md) |
| 85 | + |
| 86 | +2. `首资源包较大` |
| 87 | +- 条件: `assetContentLength`超过15 * 1024 * 1024,即未压缩的首资源包超过15MB |
| 88 | +- 优化手段: [首资源包下载与体积](StartupOptimization.md#221-首资源包下载与体积) |
| 89 | + |
| 90 | +3. `首资源包未开启服务器压缩` |
| 91 | +- 条件: `useContentEncoding`值为`false`,服务器未开启br或gzip |
| 92 | +- 优化手段: [首资源包下载与体积](StartupOptimization.md#221-首资源包下载与体积) |
| 93 | + |
| 94 | +4. `callmain耗时较长,请用安卓cpuprofile分析热点函数` |
| 95 | +- 条件: iOS平台`callmainCost>1500`或安卓平台`callmainCost>3000` |
| 96 | +- 优化手段: [引擎初始化与开发者首帧逻辑](StartupOptimization.md#223-引擎初始化与开发者首帧逻辑) |
| 97 | + |
| 98 | +### 预下载检测 |
| 99 | +检查预下载列表使用情况,分为引擎初始化完成(`callmain`)和检测完成时两个结果 |
| 100 | + |
| 101 | +> vconsole输出如下:`预下载基本信息: xxx , callmain完成时预下载信息: xxx`。其中xxx为js对象 |
| 102 | +
|
| 103 | +#### 监控指标 |
| 104 | +``` |
| 105 | +{ |
| 106 | + loadedCount: number; // 已预下载完成数量 |
| 107 | + loadingCount: number; // 正在预下载数量 |
| 108 | + loadedSizeStr: string; // 已预下载完成大小字符串表示, eg: 10.1MB |
| 109 | + loadedSize: number; // 已预下载完成大小,bytes |
| 110 | + hitCacheCount: number; // 命中CDN缓存的数量 |
| 111 | + useH2: boolean; // 是否启用HTTP2 |
| 112 | + useContentEncoding: boolean; // 是否开启了服务器压缩 |
| 113 | + preloadListLength: number; // 预下载资源个数 |
| 114 | +} |
| 115 | +``` |
| 116 | + |
| 117 | +#### 优化建议概览 |
| 118 | +1. `未使用预下载能力` |
| 119 | +- 条件: `preloadListLength=0`; 即导出时未配置预下载列表 |
| 120 | +- 优化手段: [使用预下载功能](UsingPreload.md) |
| 121 | + |
| 122 | +2. `已发起预下载,但未完成,请检查预下载资源是否过大,或是否下载过慢` |
| 123 | +- 条件: 引擎初始化完成时,`loadingCount != 0` 且 `loadedCount = 0`; 表示预下载已发起但未完成 |
| 124 | +- 优化手段: [使用预下载功能-注意事项第五点](UsingPreload.md#注意事项),预下载文件体积不应过大,将优先需要使用的资源放到列表头部 |
| 125 | + |
| 126 | +3. `预下载资源较小,请将大资源调整到预下载列表顶部` |
| 127 | +- 条件: 引擎初始化完成时 `loadedSize < 1 * 1024 * 1024 (1MB)` 或停止检测时 `loadedSize < 5 * 1024 * 1024 (5MB)` |
| 128 | +- 优化手段: 适当增加预下载资源大小 |
| 129 | + |
| 130 | +4. `预下载资源个数较多` |
| 131 | +- 条件: `preloadListLength > 15`;即预下载列表数大于15 |
| 132 | +- 优化手段: 减小预下载个数 |
| 133 | + |
| 134 | +5. `预下载资源量较大` |
| 135 | +- 条件: `loadedSize > 20 * 1024 * 1024 (20MB)`;即总预下载大小超过20MB |
| 136 | +- 优化手段: 减小预下载资源量,过大的资源下载会造成带宽抢占,推荐由游戏自行控制加载时机 |
| 137 | + |
| 138 | +### wasm分包检测 |
| 139 | +使用wasm代码分包后,检查wasm分包代码的加载时机,加载分包造成的卡顿时长,用来分析分包收集是否合理。若加载时机过早、阻塞时间过长,则需要优化。 |
| 140 | + |
| 141 | +> tips: 在新包做wasm分包期间可能会频繁提示优化建议 |
| 142 | +
|
| 143 | +#### 监控指标 |
| 144 | +``` |
| 145 | +{ |
| 146 | + loadSubWasmPackageStartTime: number; // 开始下载wasm子包的时间,ms |
| 147 | + loadSubWasmPackageCostTime: number; // (仅安卓)加载子包耗时,ms |
| 148 | + loadDurationCallmain: boolean; // 是否在引擎初始化期间加载子包 |
| 149 | + maxFetchPendingTime: number; // (仅iOS高性能)最大阻塞时间,ms。iOS高性能加载子包代码时会卡顿 |
| 150 | + costTimeAfterCallmain: number; // 引擎初始化完成后多长时间开始加载子包,ms |
| 151 | +} |
| 152 | +``` |
| 153 | + |
| 154 | +#### 优化建议概览 |
| 155 | +1. `wasm子包在callmain期间加载,请使用分包工具继续收集` |
| 156 | +- 条件: `loadDurationCallmain=true` |
| 157 | +- 优化手段: 分包收集不足,使用分包工具[继续迭代](WasmSplit.md#迭代流程) |
| 158 | + |
| 159 | +2. `wasm子包加载时机过早,请使用分包工具继续收集` |
| 160 | +- 条件: `costTimeAfterCallmain < 30000 (30s)` |
| 161 | +- 优化手段: 游戏前期不应加载子包,当前期出现子包加载,则需要[继续迭代](WasmSplit.md#迭代流程) |
| 162 | + |
| 163 | +3. `缺失函数过多,请使用分包工具继续收集` |
| 164 | +- 条件: `maxFetchPendingTime > 2000` |
| 165 | +- 优化手段: [iOS高性能模式收集](WasmSplit.md#ios-高性能模式收集很卡) [继续迭代](WasmSplit.md#迭代流程) |
| 166 | + |
| 167 | + |
| 168 | +### 网络信息检测 |
| 169 | +检查可缓存资源配置、CDN配置、并发数数、请求量、资源量 |
| 170 | + |
| 171 | +#### 监控指标 |
| 172 | +``` |
| 173 | +{ |
| 174 | + useH2: boolean, // 是否开启HTTP2.0 |
| 175 | + useContentEncoding: boolean, // 是否开启服务器压缩 |
| 176 | + cacheSettings: boolean, // settings.json是否自动缓存 |
| 177 | + cacheCatalog: boolean, // catalog.json是否自动缓存 |
| 178 | + appendHashToCatalog: boolean, // catalog.json是否带上了hash或其他用于区分版本的信息 |
| 179 | + requestCataHash: boolean, // 是否请求了catalog.hash文件用于资源热更新 |
| 180 | + requestBundleSettings: boolean, // 是否请求了settings.json |
| 181 | + requestBundleCatalog: boolean, // 是否请求了catalog.json |
| 182 | + loadCount: number, // 已发起请求数 |
| 183 | + loadedCount: number, // 已完成请求数 |
| 184 | + loadedSizeStr: string, // 请求回包总大小的字符串表示,eg: 10.1MB |
| 185 | + loadedSize: number, // 请求回包总大小,bytes |
| 186 | + hitCacheCount: number, // 命中CDN缓存个数 |
| 187 | + cacheableCount: number, // 可自动缓存个数 |
| 188 | + loadFromCacheCount: number, // 使用本地缓存的个数 |
| 189 | + startTime: number, // 首个请求开始时间 |
| 190 | + duration: number, // 监控时长 |
| 191 | + networkTime: number, // 有网络请求的总时长 |
| 192 | + maxLoadingCount: number, // 最大并发数,基于业务侧统计,会大于10个,表示有请求会排队 |
| 193 | + avgLoadingCount: number, // 平均并发数 |
| 194 | + loadedTasks: IBaseRequestInfo[], // 已下载完成请求详细信息 |
| 195 | +} |
| 196 | +
|
| 197 | +// 请求详细信息如下 |
| 198 | +interface IBaseRequestInfo { |
| 199 | + url: string; // 请求URL |
| 200 | + startTime: number; // 请求开始时间 |
| 201 | + statusCode?: number; // 服务器状态码 |
| 202 | + enableContentEncoding?: boolean; // 是否开启了服务器压缩 |
| 203 | + endTime?: number; // 请求介绍时间 |
| 204 | + duration?: number; // 请求耗时 |
| 205 | + protocol?: string; // 网络协议,h2或http1.1 |
| 206 | + receivedBytedCount?: number; // 回包大小,bytes |
| 207 | + hitCache?: boolean; // 是否命中CDN缓存 |
| 208 | + isReadFromCache?: boolean; // 是否使用本地缓存 |
| 209 | + cacheable?: boolean; // 是否自动缓存的资源 |
| 210 | +} |
| 211 | +
|
| 212 | +``` |
| 213 | + |
| 214 | +#### 优化建议概览 |
| 215 | +1. `未开启http2` |
| 216 | +- 条件: `useH2=false` |
| 217 | +- 优化手段: 服务器开启HTTP2.0,通过多路复用和头部压缩的特性,能提升细碎文件的下载效率 |
| 218 | + |
| 219 | +2. `未命中CDN缓存` |
| 220 | +- 条件: `hitCacheCount=0` |
| 221 | +- 优化手段: 发布新版本时,建议进行CDN预热,避免直接从源站拉取资源 |
| 222 | + |
| 223 | +3. `请勿缓存settings.json` |
| 224 | +- 条件: `requestBundleSettings=true` 且 `cacheSettings=true` |
| 225 | +- 优化手段: Addressables的`settings.json`文件用来记录打包配置,不应该缓存到本地。取消此文件的自动缓存,[哪些资源会自动缓存](FileCache.md#二哪些资源会自动缓存) |
| 226 | + |
| 227 | +4. `可将catalog.json配置为可缓存文件` |
| 228 | +- 条件: `requestBundleCatalog=true` 且 `cacheCatalog=false` |
| 229 | +- 优化手段: Addressables的`catalog.json`记录了所有资源文件的描述信息和依赖关系,一般大小较大,推荐缓存到本地,[哪些资源会自动缓存](FileCache.md#二哪些资源会自动缓存) |
| 230 | + |
| 231 | +5. `catalog.json被缓存且无hash/版本信息, 会导致无法更新` |
| 232 | +- 条件: `requestBundleCatalog=true` 且 `cacheCatalog=true` 且 `appendHashToCatalog=false` |
| 233 | +- 优化手段: `catalog.json`缓存到本地若无版本标识,会导致无法更新到最新版本, [缓存规则](FileCache.md#三缓存规则) |
| 234 | + |
| 235 | +6. `请勿请求catalog.hash来做资源热更新,小游戏平台不支持` |
| 236 | +- 条件: `requestCataHash=true` |
| 237 | +- 优化手段: `catalog.hash`记录了`catalog.json`的hash,用来热更新资源,但小游戏平台不支持,推荐使用`catalog.json`文件名带hash的方式来管理catalog版本,参见建议第五点 |
| 238 | + |
| 239 | +7. `可缓存文件过少,检查缓存配置` |
| 240 | +- 条件: `cacheableCount < loadCount / 2`,可缓存资源小于总请求数的一半 |
| 241 | +- 优化手段: 检查[缓存配置](FileCache.md#二哪些资源会自动缓存),是否资源文件大部分未缓存。提高可缓存数量 |
| 242 | + |
| 243 | +8. `网络并发数过少` |
| 244 | +- 条件: `avgLoadingCount < 5`,平均并发数小于5 |
| 245 | +- 优化手段: 并发数较少可能导致细碎文件较多时网络利用率不高,业务侧提高请求并发数 |
| 246 | + |
| 247 | +9. `网络未充分利用` |
| 248 | +- 条件: `networkTime / duration < 0.7`, 网络时间占监控时长占比不足70% |
| 249 | +- 优化建议: 可能由于游戏业务初始化逻辑较重,cpu繁忙,在cpu繁忙时未充分利用网络空闲;建议:在开始长耗时逻辑前,发起资源下载任务,充分利用网络 |
| 250 | + |
| 251 | + |
| 252 | +### 帧率检测 |
| 253 | +检查是否有大长帧,标记大长帧出现的位置,辅助定位是cpu耗时还是网络耗时导致启动慢 |
| 254 | + |
| 255 | +#### 监控指标 |
| 256 | +``` |
| 257 | +{ |
| 258 | + frames: string[]; // 长耗时的帧 |
| 259 | + frameCount: number; // 长耗时帧的个数 |
| 260 | + longestFrame: { // 最长帧信息 |
| 261 | + frame: string; // 帧数 |
| 262 | + frameCost: number; // 单帧耗时, ms |
| 263 | + runtime: number; // 游戏运行时长, ms |
| 264 | + }; |
| 265 | + frameInfo: IWrongFrame; // 长耗时帧信息 |
| 266 | + totalJankTime: number; // 总卡帧时长, ms |
| 267 | + currentRuntime: number; // 当前游戏运行时长, ms |
| 268 | + jankRate: number; // 卡顿率 |
| 269 | +} |
| 270 | +``` |
| 271 | + |
| 272 | +#### 优化建议概览 |
| 273 | +卡帧问题,均需要使用cpuprofile定位。[使用Android CPU Profiler性能调优](AndroidProfile.md)、[使用Unity Profiler性能调优](UnityProfiler.md) |
| 274 | + |
| 275 | + |
| 276 | +1. `存在长耗时帧cost=xxxms,runtime=xxxms` |
| 277 | +- 条件: `longestFrame.frameCost > 1000 (1s)` |
| 278 | + |
| 279 | +2. `总卡顿时长xxxms` |
| 280 | +- 条件: `totalJankTime > 5000 (5s)` |
| 281 | + |
| 282 | +3. `卡顿时长占比xx%` |
| 283 | +- 条件: `jankRate > 0.3` |
0 commit comments