往期推文全新看点(文中附带全新鸿蒙5.0全栈学习笔录)
✏️ 鸿蒙应用开发与鸿蒙系统开发哪个更有前景?
✏️ 嵌入式开发适不适合做鸿蒙南向开发?看完这篇你就了解了~
✏️ 对于大前端开发来说,转鸿蒙开发究竟是福还是祸?
✏️ 鸿蒙岗位需求突增!移动端、PC端、IoT到底该怎么选?
✏️ 记录一场鸿蒙开发岗位面试经历~
✏️ 持续更新中……
概述
调优是指对应用程序进行优化和改进,以提高其运行速度、资源利用效率和响应时间的过程。通过对应用程序进行细致的调优,可以使应用程序更高效、更稳定。在当今数字化时代,随着应用程序变得越来越复杂和庞大,调优变得尤为重要。一个经过有效调优的应用程序不仅可以更高效地运行,还能提高应用的稳定性,提升程序的效率,减少资源的浪费,从而为用户带来更好的体验。因此,了解调优的方法和常用工具对于开发人员至关重要。
调优的过程通常包括现场复现、问题分析、确定解决方案和性能测试这几个关键步骤。现场复现是指在具体环境中复现问题,以便更好地分析和解决。问题分析阶段则是深入分析应用程序的性能瓶颈和问题根源,为后续优化提供指导。确定解决方案是根据问题分析的结果,制定具体的优化方案和措施。最后,性能测试是验证调优效果的关键步骤,通过对优化后的应用程序进行性能测试,评估改进效果。
为了有效进行调优工作,需要借助一些常用的工具。例如,性能分析工具DevEco Profiler可以监测应用的性能指标、录制Trace记录,开发者可以通过分析Trace数据,发现代码中的性能瓶颈,进而优化性能。另外,HiDumper命令行工具可以获取UI界面组件树的信息、内存、CPU等使用情况,从而帮助开发者定位问题并进行优化。
本文将介绍调优的方法、常用的工具,开发者可以更好地分析和解决应用程序中的性能问题,提升用户体验,实现应用程序的高效稳定运行。
调优分析步骤
调优分析方法在应用程序优化过程中起着至关重要的作用。这些方法帮助开发人员识别问题、定位瓶颈,并最终改进系统性能。具体调优分析方法如下:
- 现场复现:现场复现是调优分析的第一步,通过对报错、卡顿等问题进行复现,开发人员可以确认问题现象和性能瓶颈。通过复现问题,开发人员可以更好地理解和定位问题,为后续的问题分析提供信息。
- 问题分析:问题分析是调优过程中的关键步骤。在确认问题现象后,开发人员需要参考相关可观测性数据,然后深入分析和诊断应用程序自身的问题。通过系统性的分析,开发人员可以定位问题的根源,为后续的优化工作奠定基础。
- 确定解决方案:在问题分析阶段确定问题根源后,开发人员需要制定具体的解决方案。这包括回归代码本身,结合业务场景和API,找出适合的优化方案。
- 性能测试:性能测试是验证调优效果的最后一步。通过对优化后的应用程序进行测试和验证,开发人员可以确保应用的性能得到了有效提升。性能测试可以帮助开发人员评估改进效果,发现潜在问题,并进一步优化应用性能。
综上所述,调优分析是应用程序优化过程中不可或缺的步骤。通过现场复现、问题分析、确定解决方案和性能测试这些步骤,开发人员可以识别和解决应用性能问题,从而提升应用程序的效率和稳定性。
常见工具
DevEco Profiler
性能调优工具可以帮助开发者找出应用中的性能问题,DevEco Studio提供许多常用的性能调优工具,具体如下所示:
- 启动分析工具Launch Profiler: 分析启动过程中各阶段的性能问题。Launch主要用于分析应用或服务的启动耗时,分析启动周期各阶段的耗时情况、核心线程的运行情况等,协助开发者识别启动缓慢的原因。
- 帧率分析工具Frame Profiler: 用于深度分析应用或服务卡顿丢帧的原因。Frame用于录制GPU数据信息,录制完成展开之后的子泳道对应录制过程中各个进程的帧数据,主要用于深度分析应用或服务卡顿丢帧的原因。Frame会对Trace进行录制和解析,进而可以分析应用运行的性能及其瓶颈。
- 耗时分析工具Time Profiler: 在应用/服务运行时,展示热点区域内基于 CPU 和进程耗时分析的调用栈情况。Time可在应用/服务运行时,展示热点区域内基于CPU和进程耗时分析的调用栈情况,并提供跳转至相关代码的能力,使开发者更便捷地进行代码优化。
- 内存分析工具Allocation Profiler: 实时监测应用或服务内存使用情况。开发者可以使用Allocation内存分析器,识别可能会导致应用卡顿、内存泄漏、内存抖动的问题。
- 内存快照Snapshot Profiler: 用于分析应用程序内存使用情况。内存快照(Snapshot)是一种用于分析应用程序内存使用情况的工具,通过记录应用程序在运行时的内存快照,可以快速查看应用程序在某一时刻的内存占用情况以及内存占用详情。
- 应用性能分析工具CPU Profiler: 该工具可以监测应用的CPU使用情况,为开发者提供性能采样分析手段,可在不插桩情况下获取调用栈上各层函数的执行时间,并展示在时间轴上。
- ArkWeb分析工具:DevEco Profiler提供ArkWeb分析模板,可以结合ArkWeb执行流程的关键trace点来定位问题发生的阶段。
- Network分析工具:DevEco Profiler提供Network模板,帮助用户在应用运行过程中查看http协议栈网络信息,包括请求分段耗时以及请求具体内容,方便对网络问题进行调优。
其他常见的性能调优工具,包括HiDumper、SmartPerf。
- 性能优化工具HiDumper:HiDumper是为开发、测试人员提供的系统信息获取工具,帮助开发者分析、定位问题。在应用开发过程中,开发者可以使用HiDumper命令行工具获取UI界面组件树信息,配合ArkUI Inspector等图形化工具定位布局性能问题;还可以使用该命令行工具获取如内存和CPU使用情况等各项系统数据,对应用性能进行评估。
- 性能功耗调优工具SmartPerf:SmartPerf是一款深入挖掘数据、细粒度展示数据的性能功耗调优工具,可采集CPU调度、频点、进程线程时间片、堆内存、帧率等数据,采集的数据通过泳道图清晰地呈现给开发者,同时通过GUI以可视化的方式进行分析。工具当前为开发者提供了五个分析模板,分别是帧率分析、CPU/线程调度分析、应用启动分析、TaskPool分析、动效分析。
ArkUI Inspector
介绍
开发者可以使用 Inspector双向预览,在DevEco Studio上查看应用在真机上的组件布局,并通过查看多次操作后的界面状态,快速分析定位状态变量、组件嵌套层次、UI界面布局存在的问题等。
Trace打点信息说明
HarmonyOS的DFX子系统提供了为应用框架以及系统底座核心模块的性能打点能力,每一处打点即是一个Trace,其上附带了记录执行时间、运行时格式化数据、进程或线程信息等。开发者可以使用DevEco Studio的Frame对Trace进行解析,并在其绘制的泳道图中识别关键渲染流程。
线程状态转化流程
在HarmonyOS中,Trace记录的线程状态主要分为运行中(Running)、可运行(Runnable)、休眠中(Sleep)、IO阻塞下不可中断的睡眠态(Uninterruptible Sleep - IO)、不可中断的睡眠态(Uninterruptible Sleep - non IO)。其状态转化图如下所示:
图1线程状态转化图
通过Trace点位信息识别线程状态
Trace 会用不同的颜色来标识不同的线程状态,在每个方法上面都会有对应的线程状态来标识目前线程所处的状态,通过查看线程状态可以分析出当前的性能瓶颈。
(1)运行中(Running)
运行中(Running)表示只有在该状态的线程才可能在CPU上运行。而同一时刻可能有多个线程处于可执行状态,这些线程的task_struct结构被放入对应CPU的可执行队列中(一个线程最多只能出现在一个CPU的可执行队列中)。调度器的任务就是从各个CPU的可执行队列中分别选择一个线程在该CPU上运行。
(2)可运行(Runnable)
可运行(Runnable)表示线程可以运行但当前没有安排,在等待CPU调度。Runnable状态的线程状态持续时间越长,则表示CPU的调度越忙,没有及时处理到这个任务。
(3)休眠中(Sleep)
休眠中(Sleep)表示线程没有工作要做,可能是因为线程在互斥锁上被阻塞,也有可能是在等某些操作返回,一般是在等事件驱动。
(4)IO阻塞下不可中断的睡眠态(Uninterruptible Sleep - IO)
IO阻塞下不可中断的睡眠态(Uninterruptible Sleep - IO)表示线程在I/O上被阻塞或等待磁盘操作完成。当系统处于低内存状态时,申请内存的时候可能会触发page fault,从而导致有大量的不可中断的睡眠态出现。在Linux系统的page cache链表中,有时会出现一些还没准备好的page(即还没把磁盘中的内容完全地读出来) ,而正好此时用户在访问这个page时就会出现page fault。
(5)不可中断的睡眠态(Uninterruptible Sleep - non IO)
不可中断的睡眠态(Uninterruptible Sleep - non IO)表示线程在另一个内核操作(通常是内存管理)上被阻塞。一般是陷入了内核态,有些情况下是正常的,有些情况下是不正常的,需要按照具体的情况去分析。
渲染流程
在HarmonyOS中,图形系统采用了统一渲染的模式,遵循着一个典型的流水线模式,以90Hz刷新率为例,每个Vsync周期是11.1ms,整个过程如下图所示。如果是60Hz,每个Vsync的周期是16.7ms;如果是120Hz,则每个Vsync的周期是8.3ms。
图290Hz刷新率渲染流程
在整个渲染流程中,首先是由应用侧响应消费者的屏幕点击等输入事件,由应用侧处理完成后再提交给Render Service,由Render Service协调GPU等资源处理后,再将最终的图像统一送到屏幕上进行显示。
- 应用侧(App)处理用户的屏幕点击等输入事件,生成当前界面描述的数据结构。其中,界面描述数据包括UI元素的位置,大小,资源,UI元素的绘制指令,动效属性等。
- Render Service(渲染服务部件)是图形栈中负责界面内容绘制的模块,其主要职责就是对接ArkUI框架,支撑ArkUI应用的界面显示,包括控件、动效等UI元素。Render Service的RenderThread线程在Vsync下触发UI绘制,绘制过程包含3个阶段:Animation动效,Draw描画和Flush提交。
- Display是显示屏幕的抽象概念,可以是实际的物理屏也可以是虚拟屏。
其中应用侧的渲染流程如下图所示,了解ArkUI的渲染流程有助于定位应用侧的卡顿问题出现在哪个环节:
图3ArkUI渲染管线结构与Frame Insight性能打点
- Animation:动画阶段,在动画过程中会修改相应的FrameNode节点触发脏区标记,在特定场景下会执行用户侧ets代码实现自定义动画;
- Events:事件处理阶段,比如手势事件处理。在手势处理过程中也会修改FrameNode节点触发脏区标记,在特定场景下会执行用户侧ets代码实现自定义事件;
- UpdateUI:自定义组件(@Component)在首次创建挂载或者状态变量变更时会标记为需要rebuild状态,在下一次Vsync过来时会执行rebuild流程,rebuild流程会执行程序UI代码,通过调用View的方法生成相应的组件树结构和属性样式修改任务。
- Measure:布局包装器执行相关的大小测算任务。
- Layout:布局包装器执行相关的布局任务。
- Render:绘制任务包装器执行相关的绘制任务,执行完成后会标记请求刷新RSNode绘制
- SendMessage:请求刷新界面绘制。
在整个处理流程中,应用侧和Render Service侧都有可能出现卡顿导致最终用户观测到丢帧的可能,这两种情况分别为AppDeadlineMissed和RenderDeadlineMissed。一般而言,前者可能是应用逻辑处理代码不够高效导致的,后者可能是界面结构过于复杂或者GPU负载过大等原因导致的。这两个故障模型通过Frame模板都可以直观地看到。相应的故障模型如下面两幅图所示。
图4应用卡顿导致丢帧的故障模型
**图5 **Render Service卡顿导致丢帧的故障模型
通过Trace识别关键渲染流程
一帧的渲染流程中的UI后端引擎的常用Trace的含义如下图所示:
图6UI后端引擎渲染Trace泳道图
各部分介绍见下表:
表1常见Trace说明表
图形图像子系统中的Render Service,是负责界面内容绘制的部件,处理由各个应用提交的统一渲染任务,将不同应用渲染的图层进行合并、送显。在收到每个Vsync周期信号时,首先处理应用提交的指令,包括应用渲染树节点的新增、删除、修改,然后进行动画计算和遮挡计算,以上是为了对统一渲染树进行更新。接下来开始对渲染树执行绘制,首先预处理每个节点,计算绝对位置和脏区信息,然后针对脏区进行绘制,优先使用硬件合成器进行绘制,当遇到无法合成绘制的,交由GPU执行重绘,绘制的所有结果都将存入屏幕缓冲区,最后将绘制结果提交送显、上屏展示。
Vsync信号刷新时的Trace泳道图如下所示:
图7RS侧渲染Trace泳道图
各部分介绍如下表:
表2Render Service侧渲染Trace说明表
序号 | Trace | 描述 |
---|---|---|
1 | RSMainThread::DoComposition | 合成渲染树上各节点图层 |
2 | RSMainThread::ProcessCommand | 处理client端指令 |
3 | Animate | 动画处理 |
4 | ProcessDisplayRenderNode[x] | 单个显示器画面的绘制流程 |
5 | Repaint | 硬件合成器合成绘制 |
6 | RenderFrame | GPU执行绘制 |
7 | SwapBuffers | 刷新屏幕缓冲区 |
8 | Commit | 绘制结果提交上屏 |
通过Trace识别懒加载渲染流程
懒加载使用LazyForEach实现,LazyForEach从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。当LazyForEach在滚动容器中使用时,框架会根据滚动容器可视区域按需创建组件。当组件滑出可视区域外时,框架会进行组件销毁以降低内存占用。
下图是抓取的懒加载过程中一帧的Trace泳道图:
序号 | Trace | 参数说明 | 描述 |
---|---|---|---|
1 | OnIdle, targettime:%" PRId64 " | 时间戳,在这个时间之前完成该任务 | idle事件循环中检查是否有新的事件需要处理,如果有,则将任务调度器加入UI线程中并执行预测任务 |
2 | Builder:BuildLazyItem [%d] | 需创建的项目索引 | 在需要时创建项,并进行缓存 |
3 | CustomNode:BuildRecycle %s | JS视图名称 | 触发复用渲染 |
4 | ExecuteJS | 执行JS代码 | |
5 | List predict | 添加预测布局任务 | |
6 | Layout | 当前帧节点布局 |
添加自定义Trace信息
开发者可以根据业务需求,使用HiTraceMeter进行自定义Trace打点跟踪,具体使用细节可参考《使用HiTraceMeter跟踪性能(ArkTS/JS)》和《使用HiTraceMeter跟踪性能(C/C++)》。
添加自定义Trace后,可在SmartPerf-Host调试工具上查看,自定义Trace将以独立泳道的形式呈现在对应打点的进程下。 下图两条泳道使用了startTrace和finishTrace方法,表示程序运行过程中,指定标签从调用startTrace到调用finishTrace的耗时统计。图中记录了CUSTOM_TRACE_TAG_1和CUSTOM_TRACE_TAG_2两个标签,先后呈现了2个标签的耗时统计。
自定义Trace示例:
自定义状态值示例: