鸿蒙南向(OpenHarmony)应用性能优化-常用Trace使用教程 ...

打印 上一主题 下一主题

主题 941|帖子 941|积分 2833

  鸿蒙NEXT开发实战往期必看文章:
  一分钟了解”纯血版!鸿蒙HarmonyOS Next应用开发!
  “非常详细的” 鸿蒙HarmonyOS Next应用开发学习路线!(从零基础入门到精通)
  HarmonyOS NEXT应用开发案例实践总联合(持续更新......)
  HarmonyOS NEXT应用开发性能优化实践总结(持续更新......)
  
  概述

  OpenHarmony的DFX子体系提供了为应用框架以及体系底座焦点模块的性能打点能力,每一处打点即是一个Trace,其上附带了记载实验时间、运行时格式化数据、进程或线程信息等。开发者可以使用SmartPerf-Host调试工具对Trace举行剖析,在其绘制的泳道图中,对应用运行过程中的性能热点举行分析,得出优化方案。本文旨在介绍OpenHarmony中常用的Trace,表明它们的寄义和用途,并阐述如何通过这些Trace来辨认匿伏的性能问题。同时,我们还将详细介绍Trace的工作原理,帮助读者更好地理解这些Trace及如何实现性能数据的采集和分析。通过本文的阅读,读者将对OpenHarmony中的Trace有一个深入的了解,为应用程序性能优化提供有力支持。
  常用Trace及寄义

  下面将从渲染流程入手,共同常用场景介绍常用Trace。
  渲染流程

  与其他操作体系类似,OpenHarmony也是由Vsync信号控制每一帧绘制操作的时机。Vsync信号是一个垂直同步信号,它指示表现器在垂直空白期之后开始下一帧的革新。设备的屏幕以固定的频率发送Vsync信号,以革新率60Hz举例,则屏幕每隔16.6ms发送一次Vsync信号。在收到Vsync信号后,UI后端引擎开始预备屏幕的下一帧绘制,然后应用程序提交渲染命令,用于形貌图形绘制、纹理设置、着色器使用等。一旦应用程序提交了渲染命令,UI后端引擎会将其添加到渲染队列中,并在合适的时机实验这些渲染命令,通常会在背景线程实验,以确保主线程不被长时间壅闭。当这些渲染命令被UI后端引擎实验时,它们会被通报给图形体系Render Service举行处置惩罚,图形体系会根据命令举行相应的图形计算和渲染操作,如顶点变换、光照、纹理贴图等。在图形体系完成渲染后,渲染结果将被写入帧缓冲区。帧缓冲区是一个内存区域,存储用于表现器输出的图像数据。一旦帧缓冲区更新完成,UI后端引擎会等待直到下一个Vsync信号到来,这个过程是为了确保渲染结果在表现器垂直消隐之前预备好。当下一个Vsync信号到来时,UI后端引擎将已经预备好的帧缓冲区的内容发送给表现器,表现器根据这些数据革新自己的像素,至此完成一整个渲染周期。如图1所示。
  图1 渲染流程图
  
  

  从Trace角度来看,一帧的渲染流程如下:
  (1)Vsync信号到达;
  (2)UI后端引擎举行第一帧绘制;
  (3)向Render Service通信,传输绘制命令并哀求一帧;
  (4)Render Service对多个图层举行归并,计算革新区域,然后举行渲染和绘制本帧;
  (5)完成一帧绘制后交给屏幕。
  一帧的渲染流程中的UI后端引擎的常用Trace的寄义如图2所示。
  图2 UI后端引擎渲染Trace泳道图
  
  

  序号Trace参数说明形貌1OnVsyncEvent now:%" PRIu64 "当前时间戳–纳秒级收到Vsync信号,渲染流程开始2FlushVsync革新视图同步事件,包罗记载帧信息、革新任务、绘制渲染上下文、处置惩罚用户输入3UITaskScheduler::FlushTask革新UI界面,包罗布局、渲染和动画等4FlushMessages发送消息通知图形侧举行渲染5FlushLayoutTask实验布局任务6FlushRenderTask %zu当前页面上的必要渲染的节点的数量总渲染任务实验7Layout节点布局8FrameNode::RenderTask单个渲染任务实验9ListLayoutAlgorithm::MeasureListItem:%d当前列表项索引计算列表项的布局尺寸  图形图像子体系中的Render Service,是负责界面内容绘制的部件,处置惩罚由各个应用提交的同一渲染任务,将不同应用渲染的图层举行归并、送显。在收到每个Vsync周期信号时,起首处置惩罚应用提交的指令,包罗应用渲染树节点的新增、删除、修改,然后举行动画计算和遮挡计算,以上是为了对同一渲染树举行更新。接下来开始对渲染树实验绘制,起首预处置惩罚每个节点,计算绝对位置和脏区信息,然后针对脏区举行绘制,优先使用硬件合成器举行绘制,当碰到无法合成绘制的,交由GPU实验重绘,绘制的所有结果都将存入屏幕缓冲区,末了将绘制结果提交送显、上屏展示。
  当Vysnc信号革新时,如图3所示。
  图3 RS侧渲染Trace泳道图
  
  

  序号Trace形貌1RSMainThread:oComposition合成渲染树上各节点图层2RSMainThread:rocessCommand处置惩罚client端指令3Animate动画处置惩罚4RSMainThread::CalcOcclusion遮挡计算5ProcessDisplayRenderNode[x]单个表现器画面的绘制流程6ProcessSurfaceNode:x单个节点的合成器处置惩罚7Repaint硬件合成器合成绘制8Redraw无法举行合成,则实验重绘9RenderFrameGPU实验绘制10SwapBuffers革新屏幕缓冲区11Commit绘制结果提交上屏  懒加载

  懒加载使用LazyForEach实现,LazyForEach从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。当LazyForEach在滚动容器中使用时,框架会根据滚动容器可视区域按需创建组件。当组件滑出可视区域外时,框架会举行组件销毁以低落内存占用。图4抓取的是懒加载过程中一帧的Trace。
  图4 懒加载Trace泳道图
  
  

  序号Trace参数说明形貌1OnIdle, targettime:%" PRId64 "时间戳,在这个时间之前完成该任务idle事件循环中查抄是否有新的事件必要处置惩罚,如果有,则将任务调度器参加UI线程中并实验猜测任务2expiringItem_ count:[%zu]懒加载Item的个数预构建,包罗处置惩罚所有懒加载项3List predict添加猜测布局任务4Builder:BuildLazyItem [%d]需创建的项目索引在必要时创建项,并举行缓存5Layout[%s][self:%d][parent:%d]tag标签,当前节点在UINode树中的索引,父节点在UINode树中的索引当前帧节点布局6Build[%s][self:%d][parent:%d]tag标签,当前节点在UINode树中的索引,父节点在UINode树中的索引当前帧节点构建7CustomNode:BuildRecycle %sJS视图名称触发复用渲染8ExecuteJS实验JS代码  页面加载

  当触发页面加载时,OpenHarmony会创建一个新的页面实例,然后按照特定的程序调用页面的生命周期方法。在生命周期方法中加载页面的布局,然后将数据绑定到页面上的视图元素,使页面可以或许表现和更新数据。图5抓取的是页面加载中一帧的Trace。
  图5 页面加载帧Trace泳道图
  
  

  序号Trace参数说明形貌1PageRouterManager::RunPage页面路由预处置惩罚及加载页面2PageRouterManager:oadPage加载页面并路由3JsiDeclarativeEngine:oadPageSource加载一个JavaScript文件并将其剖析为ABC字节码4JsiDeclarativeEngine:oadJsWithModule Execute Page code : %s页面url地址实验页面代码5Build[%s][self:%d][parent:%d]tag标签,当前节点在UINode树中的索引,父节点在UINode树中的索引当前帧节点构建6CustomNode:BuildItem %sJS视图名称渲染子节点然后将其挂载到父节点上7ViewChangeCallback(%d, %d)视图宽,视图高视图变化回调  Trace实践

  以下示例接纳LazyForEach的方式遍历列表,并借助SmartPerf-Host调试工具追踪代码实验流程。 在代码示例中,使用一个List容器组件,通过懒加载方式来创建出120个IconView自定义组件。在IconView组件中,使用了Flex容器组件包罗Image和Text子组件,形成了图文混合列表。
  1. // src/main/ets/pages/LazyForEachPage.ets
  2. @Entry
  3. @Component
  4. struct LazyForEachPage {
  5.   private iconItemSourceList = new ListData();
  6.   aboutToAppear() {
  7.     // 添加120个IconItem的数据
  8.     // ......
  9.   }
  10.   build() {
  11.     Column() {
  12.       Text('懒加载示例')
  13.         .fontSize(24)
  14.         .fontColor(Color.Black)
  15.         .fontWeight(FontWeight.Bold)
  16.         .textAlign(TextAlign.Start)
  17.         .width('90%')
  18.         .height(50)
  19.       List({ space: 20 }) {
  20.         LazyForEach(this.iconItemSourceList, (item: IconItemModel) => {
  21.           ListItem() {
  22.             IconItem({ image: item.image, text: item.text })
  23.           }
  24.         }, (item: IconItemModel, index) => index.toString())
  25.       }
  26.       .divider({ strokeWidth: 2, startMargin: 20, endMargin: 20 }) // 每行之间的分界线
  27.       .width('100%')
  28.       .height('100%')
  29.       .layoutWeight(1)
  30.     }
  31.     .width('100%')
  32.     .height('100%')
  33.     .alignItems(HorizontalAlign.Center)
  34.   }
  35. }
  36. <strong>ts</strong>
复制代码
  1. // src/main/ets/view/IconView.ets
  2. @Component
  3. export struct IconItem {
  4.   image: string | Resource = '';
  5.   text: string | Resource = '';
  6.   build() {
  7.     Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Center, alignContent: FlexAlign.Center }) {
  8.       Image(this.image)
  9.         .height(40)
  10.         .width(40)
  11.         .objectFit(ImageFit.Contain)
  12.         .margin({
  13.           left: 15
  14.         })
  15.       Text(this.text)
  16.         .fontSize(20)
  17.         .fontColor(Color.Black)
  18.         .width(100)
  19.         .height(50)
  20.         .textAlign(TextAlign.Center)
  21.     }
  22.     .width('100%')
  23.     .height(50)
  24.   }
  25. }
  26. <strong>ts</strong>
复制代码
下面使用SmartPerf-Host调试工具抓取htrace文件,并生成一个跟踪泳道分析图,来了解示例代码的加载流程。跟踪泳道分析图被分为五个部门,每个部门都标注数字并框选出相应的标签,从而使得团体的过程可以或许得到更好的理解。
  图6 LazyForEach遍历的列表的泳道分析图
  
  

  接下来,逐一剖析这五个模块的详情:
  1.加载并路由LazyForEach页面
  图7 加载并路由LazyForEach页面泳道图
  
  

  

  • H:JsiDeclarativeEngine:oadPageSource加载一个 JavaScript 文件,而且剖析为 ABC 字节码;
  • H:FlushPipelineWithoutAnimation 清算渲染管道的操作;
  • H:CustomNode:OnAppear 用于构建当前 OnAppear 生命周期的操作,并实验aboutToAppear生命周期函数;
  • H:CustomNode:BuildItem LazyForEachPage 渲染子节点并挂载在 LazyForEachPage 页面上。
  2.对当前帧节点Stage,实验布局任务、实验渲染任务并通知图形侧举行渲染
  图8 对当前帧节点Stage,实验布局任务、实验渲染任务并通知图形侧举行渲染泳道图
  
  

  

  • Hayout[stage][self:1][parent:0]对当前帧节点Stage,实验布局任务;(Stage作为框架,承载着页面Page节点。因此,标签的呈现会从Stage开始)

    • H:Measure[%s][self:17][parent:16] 对Page、Column、Row、Image、Text等组件布局尺寸计算;
    • H:Builder:BuildLazyItem [0]和HistLayoutAlgorithm::MeasureListItem:0 分别为创建一个LazyItem项目和计算列表项的布局尺寸;
    • Hayout[%s][self:38][parent:37] 对Page、Column、Row、Image、Text等组件实验布局任务;

  • H:FrameNode::RenderTask 实验渲染任务;
  • H:RequestNextVSync 哀求下一帧Vsync信号。
  3.对当前帧节点Flex,实验布局任务、实验渲染任务并通知图形侧举行渲染
  图9 对当前帧节点Flex,实验布局任务、实验渲染任务并通知图形侧举行渲染泳道图
  
  

  

  • Hayout[Flex][self:63][parent:62]对当前帧节点Flex,实验布局任务;

    • H:Measure[%s][self:17][parent:16] 对Image、Text等组件布局尺寸计算;

  • H:FrameNode::RenderTask Flex渲染任务实验;
  • H:RequestNextVSync 哀求下一帧Vsync信号。
  4.构建前预处置惩罚数据及添加猜测布局任务
  图10 构建前预处置惩罚数据及添加猜测布局任务泳道图
  
  

  

  • H:Builder:BuildLazyItem [11]构建前预处置惩罚数据了11条数据;
  • Hayout[ListItem][self:76][parent:-1] 添加一条Flex、Image、Text的猜测布局;
  • H:FlushMessages 发送消息通知图形侧举行渲染。
  5.合成渲染树上各节点图层任务
  图11 合成渲染树上各节点图层任务泳道图
  
  

  

  • H:AcquireBuffer、HrocessSurfaceNode:EntryView XYWH[0 0 720 1280]获取屏幕缓冲区并绘制EntryView、SystemUi_StatusBar、SystemUi_NavigationBar等;
  • H:Repaint 硬件合成器合成绘制当前节点树。
  自定义Trace

  开发者可以根据业务需求,使用hiTraceMeter举行自定义Trace打点跟踪,现在支持ArkTS和Native,详细使用细节可参考下方链接:
     性能打点跟踪开发指导(ArkTS) 性能打点跟踪开发指导(Native)
    添加自定义Trace后,可在SmartPerf-Host调试工具上查看,自定义Trace将以独立泳道的情势呈现在对应打点的进程下。 下图两条泳道使用了startTrace和finishTrace方法,表示程序运行过程中,指定标签从调用startTrace到调用finishTrace的耗时统计。图中记载了CUSTOM_TRACE_TAG_1和CUSTOM_TRACE_TAG_2两个标签,先后呈现了2个标签的耗时统计。
  图12 自定义Trace示例
  
  

  下图两条泳道使用了TraceByValue方法,表示程序运行过程中,指定Trace在对应时间段内的状态值,状态值寄义可按需传参,开发者可以通过鼠标放置在对应数据块上,来查看详细的状态值。图中记载了CUSTOM_TRACE_TAG_2标签在红色方框标识的时间段内,打点状态值为2001。
  图13 自定义状态值示例
  
  

  性能打点原理

  Trace的生成依靠了DFX子体系中的HiTrace组件,其中包罗的hiTraceMeter模块为开发者提供体系性能打点接口,详细细节可参考下方链接:
     HiTrace组件 hiTraceMeter模块
    hiTraceMeter拥有两套开始和结束打点接口,实现对逻辑行为的耗时统计。由于耗时统计大多数以方法为单位,所以hiTraceMeter也提供了快速打点单个方法实验耗时的宏定义HITRACE_METER、HITRACE_METER_NAME、HITRACE_METER_FMT,使用它们,只必要在方法起始位置调用即可。这些宏定义依靠了方法内局部变量的生命周期,其原理是在方法开始时构造了一个打点实例,在实例构造函数中调用开始打点接口,当方法实验完毕,打点实例随着方法结束而实验析构,在实例析构函数中调用结束打点接口。
  App中的打点示例

  ArkUI框架子体系应用hiTraceMeter的例子,来源于ArkUI开发框架源码。 以下代码对hiTraceMeter举行接口封装,其原理与HITRACE_METER等类似,依靠方法内局部变量的生命周期实现快速打点。
  1. // frameworks/base/log/ace_trace.h
  2. #define ACE_SCOPED_TRACE(fmt, ...) AceScopedTrace aceScopedTrace(fmt, ##__VA_ARGS__)
  3. #define ACE_FUNCTION_TRACE() ACE_SCOPED_TRACE(__func__)
  4. class ACE_FORCE_EXPORT AceScopedTrace final {
  5. public:
  6.     explicit AceScopedTrace(const char* format, ...) __attribute__((__format__(printf, 2, 3)));
  7.     ~AceScopedTrace();
  8.     ACE_DISALLOW_COPY_AND_MOVE(AceScopedTrace);
  9. private:
  10.     bool traceEnabled_ { false };
  11. };
  12. <strong>cpp</strong>
复制代码
以下代码是革新视图同步事件,包罗记载帧信息、革新任务、绘制渲染上下文、处置惩罚用户输入。在方法开头调用宏定义ACE_FUNCTION_TRACE,将函数名FlushVsync作为Trace名称记载下来,并记载函数开始时间,在函数结束时记载函数结束时间,得出实验耗时。
  1. // frameworks/core/pipeline/pipeline_context.cpp
  2. void PipelineContext::FlushVsync(uint64_t nanoTimestamp, uint32_t frameCount)
  3. {
  4.     ACE_FUNCTION_TRACE();
  5.     // 此处省略方法内的其他业务逻辑
  6.     // ...
  7. }
  8. <strong>cpp</strong>
复制代码
RS中的打点示例

  图形子体系应用hiTraceMeter的例子,来源于图形子体系源码。 以下代码对hiTraceMeter举行接口封装。
  1. // utils/log/rs_trace.h
  2. #include "hitrace_meter.h"
  3. #define ROSEN_TRACE_BEGIN(tag, name) StartTrace(tag, name)
  4. #define RS_TRACE_BEGIN(name) ROSEN_TRACE_BEGIN(HITRACE_TAG_GRAPHIC_AGP, name)
  5. #define ROSEN_TRACE_END(tag) FinishTrace(tag)
  6. #define RS_TRACE_END() ROSEN_TRACE_END(HITRACE_TAG_GRAPHIC_AGP)
  7. #define RS_TRACE_NAME(name) HITRACE_METER_NAME(HITRACE_TAG_GRAPHIC_AGP, name)
  8. #define RS_TRACE_NAME_FMT(fmt, ...) HITRACE_METER_FMT(HITRACE_TAG_GRAPHIC_AGP, fmt, ##__VA_ARGS__)
  9. #define RS_ASYNC_TRACE_BEGIN(name, value) StartAsyncTrace(HITRACE_TAG_GRAPHIC_AGP, name, value)
  10. #define RS_ASYNC_TRACE_END(name, value) FinishAsyncTrace(HITRACE_TAG_GRAPHIC_AGP, name, value)
  11. #define RS_TRACE_INT(name, value) CountTrace(HITRACE_TAG_GRAPHIC_AGP, name, value)
  12. #define RS_TRACE_FUNC() RS_TRACE_NAME(__func__)
  13. <strong>cpp</strong>
复制代码
以下代码在表现器画面绘制方法。在方法开头调用宏定义RS_TRACE_NAME,将函数名ProcessDisplayRenderNode与对应的表现器id组合后,作为Trace名称记载下来,同时由于其本质是使用了快速打点单个方法的宏定义HITRACE_METER_NAME,于是只必要调用一次,即可收集到ProcessDisplayRenderNode函数的实验起尽头时间,得出实验耗时。
  1. // rosen/modules/render_service/core/pipeline/rs_surface_capture_task.cpp
  2. void RSSurfaceCaptureVisitor::ProcessDisplayRenderNode(RSDisplayRenderNode &node)
  3. {
  4.     RS_TRACE_NAME("RSSurfaceCaptureVisitor::ProcessDisplayRenderNode:" +
  5.         std::to_string(node.GetId()));
  6.     // 此处省略方法内的其他业务逻辑
  7.     // ...
  8. }
复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! 更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

宁睿

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表