1# 使用SmartPerf-Host分析应用性能 2 3<!--Kit: Common--> 4<!--Subsystem: Demo&Sample--> 5<!--Owner: @mgy917--> 6<!--Designer: @jiangwensai--> 7<!--Tester: @Lyuxin--> 8<!--Adviser: @huipeizi--> 9 10## 简介 11 12SmartPerf-Host是一款深入挖掘数据、细粒度展示数据的性能功耗调优工具,可采集CPU调度、频点、进程线程时间片、堆内存、帧率等数据,采集的数据通过泳道图清晰地呈现给开发者,同时通过GUI以可视化的方式进行分析。该工具当前为开发者提供了五个分析模板,分别是帧率分析、CPU/线程调度分析、应用启动分析、TaskPool分析、动效分析。关于工具使用的更多内容可查看[SmartPerf-Host调优工具使用指导](../../device-dev/device-test/smartperf-host.md)。 13 14本文提供一些性能分析示例,介绍如何使用帧率分析和应用启动分析两个模板采集数据、分析数据,从而发现性能优化点。 15 16## 本地部署 17 18使用SmartPerf-Host进行性能分析前,需要先完成本地部署,本地部署的详细指导请参考[如何编译TraceStreamer](https://gitee.com/openharmony/developtools_smartperf_host/blob/master/trace_streamer/doc/compile_trace_streamer.md)和[SmartPerf-Host编译部署指导](https://gitee.com/openharmony/developtools_smartperf_host/blob/master/ide/README_zh.md)。在本地部署成功后,可通过https://[部署机器ip地址]:9000/application/访问,如下图。 19 20**图1** 本地部署访问页 21 22 23 24## 性能分析示例 25 26### **FrameTimeline**帧率分析 27 28SmartPerf-Host提供FrameTimeline帧率分析功能,可以抓取记录每一帧的渲染数据,自动标识其中的卡顿帧,并提供同时段的系统Trace信息,帮助开发者高效分析卡顿位置和原因。 29 30**场景示例** 31 32如下场景代码使用了Grid来实现了一个网格布局,在应用界面上下滑动时发现有卡顿掉帧现象。下文基于这个场景来介绍FrameTimeline帧率分析功能的使用方式。 33 34``` 35@Entry 36@Component 37struct Index { 38 @State children: number[] = Array.from<undefined, number>(Array(2000).fill(undefined), (_v: undefined, k) => k); 39 build() { 40 Scroll() { 41 Grid() { 42 ForEach(this.children, (item: number) => { 43 GridItem() { 44 Stack() { 45 Stack() { 46 Stack() { 47 Text(item.toString()) 48 .fontSize(32) 49 } 50 } 51 } 52 } 53 }, (item: number) => item.toString()) 54 } 55 .columnsTemplate('1fr 1fr 1fr 1fr') 56 .columnsGap(0) 57 .rowsGap(0) 58 .size({ width: "100%", height: "100%" }) 59 } 60 } 61} 62``` 63 64**抓取数据** 65 66下面介绍使用FrameTimeline帧率分析模板抓取数据的步骤: 67 681. 打开Record template -> Trace template -> FrameTimeline模板的配置开关。 69 70 **图2** FrameTimeline模板配置 71 72  73 742. 自定义配置抓取时间、抓取数据大小和结果文件名称。 75 76 **图3** 抓取配置项 77 78  79 803. 点击右上角Record开始抓取,同时在设备上复现应用掉帧或卡顿的操作过程,抓取完成后页面会自动加载trace数据。 81 82**说明:** 83 84- 在数据抓取和分析的过程中,请不要主动退出应用或者设备,否则可能导致分析任务失败。 85 86- 点击Record时,网站上方出现please kill other hdc-server!的提醒,表示设备连接失败,说明设备的hdc连接端口被占用,需要在cmd命令行中执行hdc kill指令,然后再重新连接设备进行抓取。 87 88**分析数据** 89 90完整的一个渲染流程,首先是App侧响应用户输入完成UI绘制,然后提交给Render Service,由Render Service协调GPU等资源完成渲染、合成和送显操作,在这个过程中App侧和Render Service侧都有可能出现卡顿最终导致丢帧现象。 91 92通过图4、图5、图6三组泳道数据,开发者们可以快速发现丢帧的位置,并完成初步的定界。 93 94**图4** UI + RenderService总耗时 95 96 97 98 99**图5** UI耗时 100 101 102 103 104**图6** RenderService耗时 105 106 107 108- Expected Timeline是理想帧泳道图,Actual Timeline是真实帧泳道图。 109 110- 绿色帧为正常帧,橙色帧为卡顿帧,黄色帧表示RS进程与App进程起止异常。 111 112- UI耗时(图5)显示了应用侧每一帧的处理耗时,方块的长度即为具体的耗时,RenderService耗时(图6)同理。 113 114- App侧帧/RS侧帧卡顿的计算标准为帧的实际结束时间晚于帧的期望结束时间即为卡顿。 115 116- App侧有橙色出现,需要审视UI线程的处理逻辑是否过于复杂或低效,以及是否被其它任务抢占资源。 117 118- RS侧有橙色出现,需要审视界面布局是否过于复杂,可以使用布局检查器ArkUI Inspector工具和HiDumper命令行工具辅助分析定位,相关指导可以参考[使用HiDumper命令行工具优化性能](./performance-optimization-using-hidumper.md)。 119 120从图5和图6结合来看可以确定场景示例明显属于App侧的帧卡顿。点击卡顿帧进行详细分析,相应的关联帧会通过线连起来,同时在Current Selection显示它的Details信息,如图7。 121 122**图7** App卡顿帧 123 124 125 126- Duration表示帧的持续时间。 127 128- Jank Type表示卡顿类型。APP Deadline Missed表示应用侧卡顿。 129 130- FrameTimeLine flows Slice表示链接FrameTimeLine关联帧。 131 132- Preceding flows Slice表示链接RS关联帧。 133 134如下图,展开的应用泳道图中,存在两个名字和Pid一样的泳道,第一个为线程的使用情况,第二个为线程内的方法栈调用情况。结合卡顿帧对应时间段的Trace数据,可以定位到FlushLayoutTask耗时过长,它的作用是重新测量和布局所有的Item。其中Layout[Gird]耗时最久,因此卡顿原因可以确定为Gird布局处理逻辑过于复杂或低效。 135 136**图8** 应用布局绘制trace数据 137 138 139 140定位到Grid布局代码段,经过分析,去除了冗余的3层stack容器,并将源数据提前处理为布局中需要的string类型,减少布局消耗。同时给Grid添加cachedCount参数结合LazyForEach进行预加载,cachedCount的值设定为一屏能够渲染的GridItem数量。优化后采用同样的方式抓取数据,得到的FrameTimeline泳道数据如图9,并且滑动过程中无卡顿丢帧现象。 141 142**图9** 优化后FrameTimeline泳道图 143 144 145 146优化后的示例代码如下: 147 148``` 149class MyDataSource implements IDataSource { // LazyForEach的数据源 150 private list: string[] = []; 151 152 constructor(list: string[]) { 153 this.list = list; 154 } 155 156 totalCount(): number { 157 return this.list.length; 158 } 159 160 getData(index: number): string { 161 return this.list[index]; 162 } 163 164 registerDataChangeListener(_: DataChangeListener): void { 165 } 166 167 unregisterDataChangeListener(): void { 168 } 169} 170@Entry 171@Component 172struct Index { 173 @State children: string[] = Array.from<undefined, string>(Array(2000).fill(undefined), (_v: undefined, k) => k.toString()); 174 @State data: MyDataSource = new MyDataSource(this.children) 175 build() { 176 Scroll() { 177 Grid() { 178 LazyForEach(this.data, (item: string) => { 179 GridItem() { 180 Text(item) 181 .fontSize(32) 182 } 183 }, (item: string) => item) 184 } 185 .cachedCount(80) 186 .columnsTemplate('1fr 1fr 1fr 1fr') 187 .columnsGap(0) 188 .rowsGap(0) 189 .size({ width: "100%", height: "100%" }) 190 } 191 } 192} 193``` 194 195### **AppStartup**应用启动分析 196 197SmartPerf-Host提供了AppStartup功能,以便于分析应用启动时各个阶段耗时情况。应用启动分析功能主要是提供应用启动分析模板,帮助系统调优人员做应用启动慢场景问题分析,快速查找系统侧启动慢阶段和耗时长调用栈信息。 198 199**场景示例** 200 201以下示例代码展示AppStartup功能。 202 203``` 204@Entry 205@Component 206struct Index { 207 @State private text: string = "hello world"; 208 private count: number = 0; 209 210 aboutToAppear() { 211 this.computeTask(); 212 } 213 214 build() { 215 Column({space: 10}) { 216 Text(this.text).fontSize(50) 217 } 218 .width('100%') 219 .height('100%') 220 .padding(10) 221 } 222 223 computeTask() { 224 this.count = 0; 225 while (this.count < 10000000) { 226 this.count++; 227 } 228 } 229} 230``` 231 232**抓取数据** 233 234使用如下步骤进行AppStartup数据的抓取: 235 2361. 切换到Flags页面,将AppStartup选项切换到Enabled,开启AppStartup模板。 237 238 **图10** AppStartup特性开关 239 240  241 2422. 切换到Record template页面,点击Trace template,开启AppStartup。 243 244 **图11** AppStartup模板配置 245 246  247 2483. Record setting内设置文件名、大小以及抓取时长。 249 250 **图12** 抓取配置项 251 252  253 2544. 点击右上角Record开始抓取,同时在设备上打开目标应用。可提前点击StopRecord完成抓取,或者等待时间自动完成抓取。抓取完成后会页面会自动加载trace数据。 255 256 **图13** 停止抓取选项 257 258  259 260**分析数据** 261 262等待分析结果自动生成。点击右上角的筛选按钮,选中AppStartup,便于查看分析。 263 264**图14** 模板数据筛选 265 266 267 268展开对应应用的泳道,找到应用启动时的时间段。选中AppStartup泳道全部阶段,可以在下方详情内看到具体阶段的耗时情况。 269 270**图15** AppStartup各阶段耗时情况——优化前 271 272 273 274- ProcessTouchEvent:点击事件输入及处理 275 276- StartUIAbilityBySCB:处理创建进程信息&创建窗口 277 278- LoadAbility:拉起进程 279 280- Application Launching:加载应用 281 282- UI Ability Launching:加载UI Ability 283 284- UI Ability OnForeground:应用进入前台 285 286- First Frame - App Phase:首帧渲染提交-应用 287 288- First Frame - Render Phase:首帧渲染提交-Render Service 289 290上图展示结果显示,执行耗时最长的是UI Ability OnForeground阶段。目前耗时Duration为323ms。 291 292**图16** UI Ability OnForeground阶段耗时——优化前 293 294 295 296在这个阶段里,通过阶段内下方泳道可以发现生命周期aboutToAppear耗时较长,点击该泳道内容可以看到具体耗时Duration,为268ms,占整个UI Ability OnForeground阶段的82%。 297 298**图17** aboutToAppear耗时——优化前 299 300 301 302查看代码后发现,在aboutToAppear生命周期函数内执行了耗时的计算任务,导致应用冷启动耗时长。 303 304随后对aboutToAppear内容进行异步延迟处理。优化后代码如下: 305 306``` 307@Entry 308@Component 309struct Index { 310 @State private text: string = "hello world"; 311 private count: number = 0; 312 313 aboutToAppear() { 314 setTimeout(() => { 315 this.computeTask(); 316 }, 0) 317 } 318 319 build() { 320 Column({space: 10}) { 321 Text(this.text).fontSize(10) 322 } 323 .width('100%') 324 .height('100%') 325 .padding(10) 326 } 327 328 computeTask() { 329 this.count = 0; 330 while (this.count < 10000000) { 331 this.count++; 332 } 333 } 334} 335``` 336 337处理后用同样的方式获取一遍数据。 338 339**图18** AppStartup各阶段耗时情况——优化后 340 341 342 343继续聚焦到aboutToAppear生命周期所在的UI Ability OnForeground阶段,目前耗时Duration为81ms。 344 345**图19** UI Ability OnForeground阶段耗时——优化后 346 347 348 349在这个阶段里,通过阶段内下方泳道可以发现需要查看的生命周期aboutToAppear,点击该泳道内容可以看到具体耗时Duration,为2ms,目前只占整个UI Ability OnForeground阶段的2.5%。 350 351**图20** aboutToAppear耗时——优化后 352 353 354 355### 应用冷启动分析 356 357如果开发者需要对冷启动阶段耗时进行拆解分析,可以使用[SmartPerf的AppStartUp](#appstartup应用启动分析)能力抓取Trace,通过网站上分段点可以快速的分析冷启动过程中的耗时瓶颈。 358 359 360 3611.1、**ProcessTouchEvent**:点击事件处理阶段,对应的trace起点为`H:touchEventDispatch` 3621.2、**StartUIAbilityBySCB**:拉起应用阶段,对应的trace起点为`H:OHOS::ErrCode OHOS::AAFwk::AbilityManagerClient::StartUIAbilityBySCB` 3631.3、**LoadAbility**:进程创建阶段,对应的trace起点为`H:virtual void OHOS::AppExecFwk::AppMgrServiceInner::LoadAbility` 3642.1、**Application Launching**:应用启动阶段,对应的trace起点为`AppMgrServiceInner::AttachApplication##{bundleName}` 3652.2、**UI Ability Launching**:UIAbility启动阶段,对应的trace打点为`MainThread::HandleLaunchAbility` 3663、**UI Ability OnForeground**:应用进入前台阶段,对应的trace打点为`AbilityThread::HandleAbilityTransaction` 3674.1、**First Frame - APP Phase**:App首帧渲染提交阶段,对应的trace打点为应用主线程`H:ReceiveVsync` 3684.2、**First Frame - Render Phase**:RS首帧渲染提交阶段,对应的trace打点为Render Service主线程中`H:ReceiveVsync` 369 370冷启动在首帧上屏之前主线程还可能存在以下几个耗时阶段,与各个应用实现相关,可能存在于2.2~4.2中的每一个阶段。 3711、应用侧业务逻辑处理。例如:验证当前登录信息是否有效、广告引擎初始化、服务初始化等。 3722、网络请求数据获取。例如:首页框架数据、首页图片数据需从网络端实时获取等。 373 3744.2阶段结束后,应用首帧已完成渲染,等待Buffer来临后即可完成上屏。部分应用此时已可展示首页内容、部分应用仅展示骨架屏信息,等待网络数据返回后会再一次进行反序列化解析,当解析完成后会再次对主页内容进行刷新。 375>**说明:** 376> 377> 具体示例可参考[提升应用冷启动速度](improve-application-cold-start-speed.md) 378 379<!--no_check-->