RN 性能调优之点击响应和完成时延分析介绍

0 评论 1267 浏览 0 收藏 32 分钟

本原创文章帖发布在华为开发者联盟社区,欢迎开发者前往访问评论交流,更多与该内容相关讨论,请点击原帖查看:

RN 性能调优之点击响应和完成时延分析介绍-华为开发者话题 | 华为开发者联盟

响应时延是指用户输入触控指令后,应用给出首个可见反馈所用的时间;完成时延是指用户输入触控指令后,界面完全刷新并达到可阅读的稳定状态所用的时间。完成时延包含响应时延,两者共同决定用户对操作流畅性的感知。

       当应用出现明显的点击无反馈、页面加载迟缓等现象时,需要分析时延链路中各阶段的耗时,并针对性优化。RN应用的页面结构、bundle加载方式与ArkTS应用差异较大,难以直接套用系统时延基线,建议以同场景的基线数据(如历史版本测量值或业务目标值)为参照判断时延是否超标。

本文是RNOH性能定位系列的第二篇,说明如何在RNOH应用中定位响应时延与完成时延问题,重点介绍业务侧问题的定位与优化;定界方法可将问题归入业务侧、RNOH框架侧或系统侧(划分标准见§5.3)。线程模型、trace采集方法、trace标记含义等基础内容见RN性能调优之基础定界与trace分析介绍;滑动过程的连续丢帧问题见RN性能调优之滑动卡顿问题介绍。

1. 指标定义

1.1 响应时延与完成时延

响应时延 = 首个响应帧上屏时间 − 输入起点
完成时延 = 达到约定完成状态的时间 − 操作起点

两项指标相互独立,应分别测量:

          • 响应时延对应交互反馈的即时性,典型反馈包括按钮按压态、页面开始移动、首个新内容上屏;

          • 完成时延对应操作的完成程度,完成状态须在测试前约定,例如列表占位全部替换、转场动画结束或关键内容可见;

          • 同一次点击的响应时延达标而完成时延超标是常见现象,两项指标应分别优化、分别评估。

1.2 明确测量的起止点

时延数值取决于起点和终点的选取方式。测量前必须写清以下四项,优化前后的对比必须使用同一组起止点:

          • 输入起点:取物理触屏时刻、系统输入事件上报时刻还是业务打点时刻,不同工具的默认值不同;

          • 响应终点:对应哪个可见变化(按压态、页面开始移动、首个新内容上屏);

          • 完成终点:对应系统帧、动画结束还是关键业务内容可见;

          • 工具与版本:记录所用工具名称、版本和已知限制。

       HarmonyOS各性能工具对起止点的默认取法和已知限制,请参考性能分析与HiSmartPerf Device 性能使用指导。

1.3 RNOH内部启动指标及其边界

RNOH提供以下内部指标,用于分解启动阶段耗时:

指标 定义 可说明什么 不能说明什么
CONTENT_APPEARED 根ArkUI节点加入 NodeContent的时刻 页面框架已挂载的时间点 像素已上屏、数据图片等内容已完整、页面可交互
ROOT_VIEW_TTI APP_STARTUP_START到 CONTENT_APPEARED的汇总耗时 RNOH内部启动阶段的分解 通用意义上的”页面可交互时间”
APP_STARTUP APP_STARTUP_START/STOP的汇总耗时 RNOH记录的启动范围 应用进程完整启动耗时

这些指标适合分解RNOH启动阶段,但不能直接等同于完成时延:CONTENT_APPEARED早于实际像素上屏,也可能早于数据、图片和关键业务内容完成。报告中应注明指标的真实起止点,避免仅按名称推断。

2. 端到端时延链路

2.1 响应时延

从用户点击到首个响应帧上屏,依次经过系统输入、事件分发、JS处理、React提交、ArkUI组件挂载和上屏,共6个时间锚点(ts0~ts5,锚点用于在系统、主线程、JS线程等不同泳道的trace上对齐同一时刻):

各锚点在trace中的对应位置:

锚点 对应trace标记 所在线程
ts0系统输入 H:service report(type为up)等系统输入事件 系统输入服务
ts1 Surface收到触摸 #RNOH::ArkUISurface::onTouchEvent::DOWN RNOH_MAIN
ts2 JS handler开始 业务自定义trace点 RNOH_JS
ts3 React提交 ReactMarker::FABRIC_COMMIT_START RNOH_JS
ts4挂载完成 ReactMarker::FABRIC_BATCH_EXECUTION_END、didMount <count> RNOH_MAIN
ts5首个响应帧上屏 App Frame首个变化帧 Render Service

并非每次响应都经过完整链路。ArkUI系统级按压态、系统级滚动可以在 JS 更新前产生反馈。分析时应从实际的首个可见变化反向定位其产生环节。

2.2 完成时延

完成时延覆盖页面加载/切换从操作开始到业务完成状态的全过程:

下图为一次真实点击跳转(1500个Text组件页面)的渲染三阶段trace,对应链路图中”容器创建 → 组件构建 → 挂载 → 上屏”各阶段的实际耗时分布:

两个要点:

       • 数据准备可与组件构建并行:页面框架渲染不必等待全部数据返回,但关键内容渲染必须等数据到位。不在端到端关键路径上的并行工作,其耗时不计入完成时延;

       • 完成时延可能包含网络和业务异步阶段:不能将整个区间都归因于渲染框架,应为数据请求开始/结束、JS render、挂载和业务完成分别打点。

3. 问题定位流程

3.1 定位流程

定位响应时延与完成时延问题通常需要以下步骤:

1. 确认时延异常:使用DevEco Profiler采集系统帧泳道,对照同场景基线数据(历史版本测量值或业务目标值)确认时延确实超标,排除测试条件不稳定因素;

2. 明确测量起止点:按§1.2约定输入起点、响应终点和完成终点;

3. 采集 trace:使用release构建复现问题,采集App Frame、RS Frame、应用主线程、RNOH_JS泳道数据(§4.1);

4. 分段定位:建立统一时间轴(§3.3),通过基线数据与问题数据的对比,识别首个显著变长的区段(§3.4);

5. 沿责任域深入分析:结合关键泳道定位耗时函数与组件(§4.4),按区段责任域展开分析(§5);

6. 实施优化并复测:参考§6优化案例实施处理,优化后重复测量确认达标;未达标则重新分段定位。

3.2 定位前准备

       • 使用release构建和release bundle,关闭Metro、HMR、Inspector、高频日志,正式测量时关闭性能浮层;

       • 固定设备、HarmonyOS/RNOH版本、刷新率策略、网络、账号、缓存和测试数据;

       • 冷启动(进程首次创建)与热启动(进程复用)、首次进入页面与再次进入页面必须分别统计;

       • 每种操作重复至少5次,报告中至少给出中位数和最差值,批量测试建议增加P90/P95分位值(90%/95% 的数据优于此值);

       • 为操作开始、请求完成、关键数据到达、动画结束和关键内容可见增加业务trace点。

       性能浮层的JS FPS/UI FPS是500ms轮询的趋势指标,适合观察持续负载,不适合定位一次几十毫秒量级的点击响应问题。

3.3 建立统一时间轴

报告中同时保留绝对时间戳和相对操作起点的毫秒数:

时间点 绝对时间戳 相对起点 来源
输入起点 ts0 0 ms 系统输入 marker
RN Surface 收到 DOWN ts1 +x ms RNOH trace
JS handler 开始 ts2 +x ms 业务/JS trace
Fabric commit ts3 +x ms RNOH marker
native batch 结束 ts4 +x ms RNOH marker
首个响应帧上屏 ts5 +x ms 系统帧
业务完成 ts6 +x ms 系统/业务 marker

如果trace中没有JS handler或业务完成点,应先补充打点,不要用名称相似但语义不同的切片代替。

3.4 分段定位

将问题数据与基线数据按同一组锚点切段、逐段比较时长,首个显著变长的区段通常比单个最长切片更接近根因:

4. 使用 Profiler 工具检测和分析

DevEco Profiler 是 DevEco Studio 提供的场景化调优工具,其中 Frame 模板可以录制应用运行过程中的关键数据,用于深度分析卡顿丢帧、耗时长等问题。本节说明在时延分析场景下的使用方法,更通用的操作说明可参考Frame 分析与官方最佳实践《点击响应时延分析》、《点击完成时延分析》。

4.1 抓取操作 trace

       1. 使用 release 或 debugOptimized 构建模式安装应用,连接设备;

       2. 打开 DevEco Studio → Profiler,选择设备与目标应用、进程,选择 Frame 模板,单击 Create Session 创建会话;

       3. 确认 RNOH trace 编译开关已开启(WITH_HITRACE_REACT_MARKER、WITH_HITRACE_SYSTRACE,配置方法见基础篇「2.2 开启 trace 编译开关」一节),否则 trace 中不会出现 ReactMarker::* 与 #RNOH::* 标记;

       4. 单击启动按钮开始录制,在设备上执行待分析操作,完成后停止录制并保存 trace 文件。

       泳道选择与录制配置的详细说明,见基础篇《RN性能调优之基础定界与 trace 分析介绍》的「2.3 抓取 trace」一节。

4.2 确定时延起点和终点

响应时延:

       • 起点:手势抬起的那一帧,对应 mmi-service 泳道中 H:service report 的 type 为 up 的事件;输入分发到达应用后,可在应用侧 H:DispatchTouchEvent(type=1)处对齐(起点到该事件之间为输入阶段耗时,见 §4.3);

       • 终点:页面变化后的第一帧,对应 RSHardwareThread 泳道 CommitAndReleaseLayers 的结束点。

完成时延:

       • 起点:与响应时延一致(点击离手);

       • 终点:trace 中无统一的明确标记,需借助录屏分析工具确定完成耗时,再从起点向后定位对应时间点;建议业务侧为完成状态(如列表占位全部替换)增加自定义 trace 点。

4.3 分解时间段

官方将点击响应拆分为输入、应用、渲染三个阶段(见《点击响应时延分析》)。RNOH 应用的”应用阶段”涉及主线程与 JS 线程协作,可进一步细分:

阶段 起点 终点 说明
输入阶段 H:service report(type=up) 应用 H:DispatchTouchEvent 起点 系统输入分发
事件分发 H:DispatchTouchEvent #RNOH::ArkUISurface::onTouchEvent::DOWN 结束 主线程触摸分发与命中测试
JS 处理 事件投递至 JS 线程(runOnQueue) FABRIC_LAYOUT / FABRIC_DIFF 结束 JS handler 与 React 提交
ArkUI挂载 FABRIC_FINISH_TRANSACTION 起点 didMount <count> 结束 mutations 挂载
渲染阶段 RS 帧 ProcessCommandUni 起点 RSHardwareThread CommitAndReleaseLayers 结束点 系统合成

       官方文档中的阶段基线面向纯 ArkUI 应用;RNOH 应用包含跨线程 JS 处理环节,评估各区段耗时应以同场景的基线数据为参照。

4.4 分析定位原因

针对待分析的时延区间,结合关键泳道定位具体问题:

          • Frame 泳道:观察应用主线程帧数据,红色帧表示该帧渲染时间超出预期。页面跳转后的首帧渲染时间往往较长,首帧出现红色属常见现象,不一定表示严重性能问题,但仍需关注;

          • RNOH_JS 线程泳道:长段 ExecuteJs 说明存在 JS 长任务,展开调用栈或火焰图定位耗时函数;

          • ArkTS Callstack / Callstack 泳道:分别提供 ArkTS 侧与 Native 侧方法调用栈,用于定位高耗时函数;

          • ArkUI Component 泳道:记录组件创建、布局、渲染耗时,识别耗时明显偏长的组件;

          • H:Animator 泳道:观察转场动画耗时,动画时长设置过长会直接增大完成时延。

       各区段的责任域划分与深入分析方法见 §5。

5. 分段定位详解

本节将 §3.4 识别出的异常区段落到具体责任域。分段定位的结论应归入业务侧、RNOH 框架侧或系统侧之一,划分标准见 §5.3。

5.1 响应时延各区段

分析前置:先确认是否缺少即时反馈

       若点击后页面没有任何视觉变化,应先补充即时反馈再深入分析:按压态、loading、骨架屏等轻量反馈位于关键路径前部,是改善响应时延最直接的手段(见案例一)。即时反馈不能替代根因定位,处理耗时过长仍需按以下区段继续分析。

ts0 → ts1:系统输入到 RN Surface

       RNOH 在 ArkUISurface.cpp 中记录 #RNOH::ArkUISurface::onTouchEvent::DOWN/MOVE/UP/CANCEL。

该区段耗时异常,通常指向系统输入分发、应用主线程调度或前序任务阻塞。此时事件尚未进入 RNOH 触摸分发,不应归因于 JS 侧。

ts1 → ts2:事件交接与 JS 排队

       主线程完成触点命中测试(确定触摸落在哪个组件上)、坐标变换和事件发射,事件随后投递到 JS 线程。复杂触摸树和变换可能增加此段耗时,但应先在调用栈中证实该区段确实偏长,再做结构优化。

该区段耗时异常,通常指向主线程被 ArkTS 业务、I/O、同步任务阻塞,或 JS 线程被长计算、GC、同步 TurboModule 占用导致事件排队。若 RNOH_JS 长时间处于 Runnable(就绪但未获得 CPU)而非 Running(执行中),应检查 CPU 竞争、QoS 和温控,而非优化 JS 代码。

ts2 → ts3:业务处理与 React 提交

       JS handler 执行业务逻辑并触发 React 更新。建议为目标 JS handler 增加自定义 trace,检查:

          • handler 是否同步解析大数据、执行循环或触发大量状态更新;

          • handler 结束后 Fabric commit(FABRIC_COMMIT_START)是否及时开始;

          • 关闭 handler 中某段业务后,响应帧是否相应提前。

ts3 → ts4:Fabric 处理与ArkUI挂载

       重点关注 FABRIC_DIFF_*、FABRIC_LAYOUT_*、#RNOH::SchedulerDelegate::performTransaction、#RNOH::MountingManager::doMount、didMount <count>、FABRIC_BATCH_EXECUTION_*,以及 ArkUI 的 measure/layout/flush 切片。

注意:FABRIC_UPDATE_UI_MAIN_THREAD_* 仅表示 synchronouslyUpdateViewOnUIThread 同步更新单个组件属性的区间,不能将 FABRIC_COMMIT_START → FABRIC_UPDATE_UI_MAIN_THREAD_END 当作通用挂载完成时延。

ts4 → ts5:上屏

       native batch 已及时结束但响应帧仍延迟上屏时,依次检查:

1. App Frame 是否已异常;

2. 应用主线程的 ArkUI measure/layout/flush;

3. RS Frame 与应用提交时间的先后;

4. GPU、图片上传、复杂图层和系统资源竞争。

App Frame 正常而 RS Frame 异常时,优先定界到系统合成侧;两侧均异常时,通过时间顺序判断是应用晚提交向后传导,还是合成阻塞反向施加压力。

5.2 完成时延关键阶段

阶段 主要观察点 常见责任域
操作到路由开始 JS handler、ArkUI路由调度 RN/ArkTS 业务
容器创建 RN Surface、ArkUI页面和组件初始化 RNOH/ArkUI业务
数据准备 网络、数据库、缓存、序列化 业务/服务侧
React 产出 render、commit、diff、layout RN 业务/三方组件
native 挂载 mutation 数量、组件创建、属性更新 RNOH/组件实现/业务规模
ArkUI/RS 上屏 App Frame、RS Frame、flush/合成 ArkUI/系统/资源
关键内容完成 图片、动画、二次请求、占位替换 业务侧

判定时注意两点:

          • 只有位于端到端关键路径并推迟完成终点的阶段才计入根因。后台并行工作即使耗时较长,也可能不影响用户看到的完成状态;

          • 若页面框架先行渲染、文本和图片随后补齐,应分别记录:请求发出与返回、数据转换与 JS render、图片下载/解码/上传、最终内容上屏。文本和图片均延迟,优先检查数据绑定或 transaction 时机;文本已显示而图片延迟,优先检查图片链路。

       RNOH 首次加载相关的启动 marker(RUN_JS_BUNDLE_*、TURBO_MODULE_SETUP_*、CONTENT_APPEARED 等)见基础篇「3.1 ReactMarker(阶段级异步 trace)」一节。

5.3 责任域划分

分段定位的结论应归入以下三类责任域之一:

责任域 典型证据 定界结论
业务侧 JS handler 长任务;React 重渲染或提交过频;列表虚拟化配置不当;图片规格过大;转场动画时长过长;网络请求发起时机滞后;缺少即时反馈;bundle 打包方式(如未使用 HBC) 业务负载或使用方式问题,可按 §6 优化案例直接处理,处理后复测验证
RNOH 框架侧 异常窗口关键路径落在 RNOH 实现;移除业务负载后的最小 RNOH 示例仍能复现;同负载下 RNOH 版本 A/B 显示明确回归 定性为 RNOH 框架问题;整理 trace、最小示例与版本对照等定界材料,需要时可向 RNOH 社区反馈
系统/图形侧 App Frame 正常而 RS Frame 单独异常;线程运行核、频率等系统调度异常 定性为系统/图形侧问题;结合系统分析工具继续排查,定界材料同上保留

两点补充说明:

       • 耗时落在 RNOH 实现上不等于框架问题。doMount、performTransaction 等切片耗时长时,先做负载缩放(节点数降为 1/2、1/4)和最小示例对照:耗时随业务规模近似等比变化的,通常仍是业务负载问题(组件数量、层级导致),应归入业务侧;

       • 三方 RN 组件异常调用栈集中在三方组件、替换或降级后恢复的,定界为三方组件问题。

6. 优化案例

本节针对响应时延与完成时延的常见问题提供三个优化案例:JS 长任务、loadBundle 耗时长,以及通过 Hermes 字节码(HBC)优化 bundle 加载。每个案例按”问题现象 → 定位分析 → 优化措施 → 效果验证”组织。优化后应复测确认时延达标,未达标则回到 §3.1 重新分段定位。更多场景的优化手段可参考页面转场与滑动场景优化与性能调优。

6.1 案例一:JS 长任务导致点击响应时延过长

问题现象:点击列表项跳转详情页,点击后长时间无按压反馈。采集数据显示响应时延约 240ms,同场景基线数据约 72ms。

定位分析:

1. 按 §3.3 建立统一时间轴:输入后 +18ms 事件已进入 ArkUISurface,系统输入分发正常;RNOH_JS 到 +173ms 才开始执行目标 handler,此前被同步数据转换占用;

2. handler 结束后 +14ms 内出现 Fabric commit,native batch 未超出帧预算,首个响应帧在 JS 提交后正常上屏,RS Frame 正常;

3. 分段定位结论:ts1→ts2(事件交接与 JS 排队)为首个显著变长的区段,根因为 JS 线程被同步数据转换阻塞。

优化措施:

          • 将点击 handler 中的同步数据转换移出关键路径,改为异步处理或延后到页面挂载后执行;

          • 为点击入口补充即时反馈,按场景组合使用:列表项按压态(组件自带,成本最低)、loading(适用于等待时长较短或不确定的场景)、骨架屏(适用于页面结构可预期的加载场景)。响应时延以首个可见反馈为终点,补充即时反馈可以避免处理耗时的波动直接影响用户感知。

       效果验证:优化后响应时延降至 78ms;通过关闭同步数据转换前后的对照数据确认了因果关系。

6.2 案例二:loadBundle 耗时长导致完成时延长

问题现象:首次点击进入 RN 页面后,页面长时间停留在过渡状态,完成时延约 1269ms;再次进入明显更快,说明耗时集中在首次加载环节。

定位分析:

1. 在 trace 中搜索 loadBundle 定位 bundle 加载执行区间(RNOH_JS 线程上的 Instance::loadBundle 区段):本例约 739ms,位于点击后的关键路径上,与容器创建、首屏渲染串行执行。如需进一步分解:bundle 下载耗时查看 ReactMarker::DOWNLOAD,bundle 执行耗时查看 ReactMarker::RUN_JS_BUNDLE;

2. bundle 执行结束后各阶段耗时正常(首屏渲染 330ms、上屏 120ms),排除渲染负载问题;

3. 结论:完成时延过长的主因是 bundle 在点击后才串行加载,首次加载的耗时会全部计入完成时延。

真实 trace 中 loadBundle 耗时长的问题形态(RNOH_JS 线程上 bundle 加载与执行区间,本例 739ms):

上图的分段耗时示意:

优化措施:

          • 预创建 RN 实例:对于使用独立 Bundle 的业务模块,在应用启动阶段提前初始化 RNInstance 并加载对应 bundle,用户点击时实例与 bundle 均已就绪;

          • FrameNode 预加载 RN 页面:利用 FrameNode 与 NodeContainer,在后台提前构建目标页面的ArkUI节点树,跳转时直接挂载显示;

          • 预加载时机选择应用启动阶段或空闲时段。bundle 加载耗时并未消失,只是移出了点击后的关键路径。

       效果验证:优化后点击进入的完成时延降至约 530ms(loadBundle 的 739ms 移出关键路径);trace 中 loadBundle 区间出现在启动阶段而非点击后的时间窗口。配合案例三的 HBC 可进一步缩短 bundle 执行本身的耗时。

6.3 案例三:使用 Hermes 字节码(HBC)优化 bundle 加载

问题现象:应用首次加载慢,trace 中 ReactMarker::RUN_JS_BUNDLE 区段(bundle 执行)耗时长;使用文本 bundle 时还存在 loadApplicationScript_StringConvert(Bundle 字符串转换)开销。

原因分析:默认打包产物为 JavaScript 文本,Hermes 加载时需要先做字符串转换,再解析、编译为字节码后才能执行,这些工作全部位于启动关键路径上。Hermes 支持在构建阶段通过 hermesc 工具将 JavaScript 预编译为字节码(HBC),应用运行时直接执行字节码:跳过解析编译步骤,缩短 bundle 执行时间;同时字节码体积更小、内存占用更低。

操作方法:打包时增加 hermesc 编译步骤,将 bundle 转为 HBC 文件:

npm run codegen && react-native bundle-harmony --dev=false --minify=true --bundle-output /tmp/bundle.harmony.js && hermesc --emit-binary /tmp/bundle.harmony.js -out ./harmony/entry/src/main/resources/rawfile/hermes_bundle.hbc

效果验证:对比优化前后 RUN_JS_BUNDLE_START 到 RUN_JS_BUNDLE_STOP 的耗时变化;文本 bundle 的字符串转换开销也随之消除。

相关文档

       • RN性能调优之基础定界与 trace 分析介绍(基础篇:线程模型、trace 采集、标记含义)

       • RN 性能调优之滑动卡顿问题介绍

🔗 官网开发者学堂视频:https://developer.huawei.com/consumer/cn/training/result?type2List=201783644516849879&orderBy=1&courseType=5

     🔗 社区DFX专题文章:  https://developer.huawei.com/consumer/cn/forum/subject/2101218731402391001

【扫码加入 HarmonyOS DFX 技术交流群】

本文由 @华为开发者联盟 授权发布于人人都是产品经理。未经作者许可,禁止转载

题图来自Unsplash,基于CC0协议

该文观点仅代表作者本人,人人都是产品经理平台仅提供信息存储空间服务

更多精彩内容,请关注人人都是产品经理微信公众号或下载App
海报
评论
评论请登录
  1. 目前还没评论,等你发挥!