RN 性能调优之基础定界与 trace 分析介绍

0 评论 2112 浏览 0 收藏 39 分钟

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

RN 性能调优之基础定界与 trace 分析介绍-华为开发者话题 | 华为开发者联盟

本文是 RN 性能定界定位的基础篇,面向 RNOH 应用开发者,介绍三方面内容:

   1. RNOH 的线程模型,以及一次页面更新经过的环节;

2. trace 的采集与查看方法;

3. RNOH 在 trace 中输出的关键标记及其含义。

完成基础篇阅读后,如需深入定位具体问题,请继续阅读:

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

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

名词说明

本文会反复用到以下名词,含义见下表,阅读过程中可随时查阅:

名词 解释
RNInstance 一个 RN 运行实例(含 JS 运行时和渲染环境),一个应用可以同时存在多个
Surface 由 RN 渲染的独立显示区域,通常对应一个页面、弹层或卡片
Fabric React Native 新架构的渲染系统,负责影子树的创建、布局、对比和挂载
影子树(ShadowTree) 组件树在ArkUI侧的中间表示,布局与对比先在影子树上完成,差异再应用到真实界面
mutations 新旧影子树对比后生成的变更指令列表(详见 §1.2)
TurboModule JS 调用ArkUI能力的模块(网络、文件、传感器等)
ArkUI 鸿蒙UI 框架,RN 的组件最终由它创建和绘制
Render Service 系统合成服务,负责将各应用的画面合成到屏幕上
App Frame / RS Frame 性能工具中的两条帧泳道:应用产帧 / 系统合成帧
trace 性能分析记录,按时间轴展示各线程在各时间段的执行内容

1. RNOH 线程模型与渲染链路

1.1 四线程模型

RNOH 内部有四类线程,各自承担不同职责:

线程 职责 QoS 优先级 trace 中如何识别
MAIN/UI 应用主线程(也是 UI 线程),每个应用进程只有一个。负责 ArkUI 组件生命周期管理(CREATE/UPDATE/INSERT/REMOVE/DELETE)、组件树管理、TurboModule 业务执行、交互事件处理 跟随系统主线程 主线程名不会被改成 RNOH_MAIN(复用主线程),需通过”应用主线程 + RNOH 调用栈”确认
JS JS 运行时线程,通过 Hermes 执行 React 代码。负责 bundle 加载、ShadowTree 创建、Yoga 布局计算、新老树 diff 生成 mutations、交互事件处理。每个 RNInstance 绑定一个独立 JS 线程 QOS_USER_INTERACTIVE(用户交互级,与 UI 渲染同档的高优先级) 搜索线程名 RNOH_JS
BACKGROUND 实验特性,开启后会将 JS 线程的部分布局、ShadowTree 比较任务迁移到该线程,降低 JS 线程负荷。由于涉及复杂线程通信,稳定性有风险,正式商用版本不要开启 未单独设置 开启后搜索线程名 RNOH_BACKGROUND;默认不创建该线程
WORKER TurboModule 工作线程,将 TurboModule 运行在独立线程,避免与主线程 UI 绘制竞争资源。需要在继承 RNAbility 后重写 getRNOHWorkerScriptUrl 方法配置 跟随 Worker 线程默认 搜索 WorkerThread

线程之间的通信方向:JS → Main 通过任务投递进入主线程事件循环;Main → JS 通过消息队列投递到 JS 线程。

下图是一次典型操作中各线程的 trace 示例(线程号 53130 为 MAIN 线程,53214/53216 为两个 JS 线程实例):

1.2 渲染三阶段

RNOH 的渲染流程继承自 React Native 新架构,分为三个阶段,分布在不同线程执行:

          • 渲染(Render):在 JS 线程中,React 执行业务代码创建 React 元素树(React Element Tree),然后通过 JSI 在 C++ 中创建 React 影子树(React Shadow Tree)。

          • 提交(Commit):影子树创建完成后,通过 Yoga 引擎进行布局计算,然后与上一次的影子树对比,生成变更指令列表(mutations)。

          • 挂载(Mount):mutations 被提交到主线程,转化为 ArkUI 组件树操作(CREATE/INSERT/UPDATE/REMOVE/DELETE),最终由 ArkUI 完成绘制和合成。

下图以一次点击跳转到 1500 个 Text 组件页面为例,展示 trace 中的渲染三阶段实例:

关于 mutations:mutations 是 Fabric 对新旧影子树做 diff 后生成的变更指令列表,每条指令描述一个组件操作(如创建 tag=42 的 Text 组件、更新 tag=10 的 View 属性、将 tag=5 插入到 tag=3 下)。主线程逐条执行这些指令来完成界面更新。

1.3 性能链路

一次 RN 页面更新通常跨越以下环节。图中标注了 ①~⑥ 序号,与 §1.2″渲染三阶段 trace 实例”图(点击跳转到 1500 个 Text 组件页面)中的标注一一对应:

各环节与图中序号、渲染三阶段、trace 标记的对应关系(trace 标记的含义见 §3):

执行环节 图中序号 渲染三阶段 所在线程 对应 trace 标记
手指点击、输入分发到 RN Surface ① 输入 RNOH_MAIN #RNOH::ArkUISurface::onTouchEvent::DOWN/UP
JS 线程响应事件、执行 React 业务代码 ② 渲染(Render) RNOH_JS #RNOH::MessageQueueThread::runOnQueue
通过 JSI 创建影子树 ③ 渲染(Render) RNOH_JS ReactMarker::FABRIC_COMMIT、cloneNode 等节点树操作
Yoga 布局计算 ④ 提交(Commit) RNOH_JS ReactMarker::FABRIC_LAYOUT、#RNOH::TextMeasurer::measure
新旧影子树对比,生成 mutations ⑤ 提交(Commit) RNOH_JS ReactMarker::FABRIC_DIFF
mutations 送主线程启动渲染 ⑥ 提交(Commit)收尾 RNOH_JS → RNOH_MAIN ReactMarker::FABRIC_FINISH_TRANSACTION(主线程开始处理事务)
mutations 挂载(CREATE/INSERT/UPDATE/REMOVE/DELETE) – 挂载(Mount) RNOH_MAIN ReactMarker::FABRIC_BATCH_EXECUTION、#RNOH::MountingManager::doMount
ArkUI 布局/绘制、Render Service 合成上屏 – 挂载(Mount)之后 RNOH_MAIN / Render Service FlushVsync 等系统 trace

图中 ①~⑥ 的逐步说明也可参考渲染三阶段。

RNOH_MAIN 就是应用主线程,RNOH 在其事件循环上注册回调,不创建新线程。在 trace 中通常需要通过应用主线程身份和 RNOH 调用栈确认,不能只按线程名匹配。RNOH_JS 是独立线程,但线程名也只是一条线索,最终仍需用调用栈和时间关系确认。

1.4 指标定义

在分析 trace 前,需要统一以下指标的定义和测量方法。

卡顿和丢帧

帧预算由问题窗口内的实际目标刷新率决定:

单帧预算(ms) = 1000 / 目标刷新率(Hz)

例如,60 Hz、90 Hz、120 Hz 对应的理论预算约为 16.7 ms、11.1 ms、8.3 ms。设备可能动态切换刷新率,因此不能固定使用 16.7 ms,也不能仅用”FPS 低于 60″判断丢帧。应在 DevEco Profiler 中读取 Display、H:PreferredFrameRate 或等价信息,并结合 App Frame、RS Frame 判断。

响应时延

响应时延描述输入事件到首个可见反馈之间的时间,其数值与起止点的选取直接相关:

          • 起点:系统工具通常取点击事件上报时刻,而不是物理触屏时刻;

          • 终点:通常取首个响应帧上屏。

       因此报告中必须记录所用工具和起止点定义。不同工具、不同版本对起止点的取法可能不同,不能把 RNOH 内部的触摸 trace 直接当作端到端响应时延。

完成时延

完成时延描述操作开始到目标页面达到工具所定义”完成状态”的时间。不同测试工具可能以页面切换后的目标帧、动画结束或占位内容加载完成作为终点。业务还可能把”关键内容可见”定义为完成。测试基线、问题复现和优化前后对比必须使用同一终点。

RNOH 的 CONTENT_APPEARED 表示根节点挂载到 ArkUI 内容容器(NodeContent);ROOT_VIEW_TTI 计算为从 APP_STARTUP_START 到 CONTENT_APPEARED 的耗时。它们是 RNOH 内部阶段标记,不证明像素已经上屏、页面内容完整,也不证明页面已经可交互。

HarmonyOS 指标和工具限制请以性能分析与HiSmartPerf Device 性能使用指导为准。

2. trace 采集与查看

2.1 RNOH 两类 trace 点

RNOH 在 trace 中留下的标记分为两类,粒度和用途完全不同:

维度 RNOHMarker(阶段级) TraceSection(函数级)
trace 中搜索 ReactMarker:: #RNOH::
粒度 标记一个完整阶段的起止(如 Bundle 加载开始→结束) 标记单个函数或代码块的执行区间
典型时间跨度 大(数十毫秒到数秒) 小(微秒到毫秒级)
是否跨线程 可以(起点和终点可以在不同线程) 不可以(限同一线程内)
编译开关 WITH_HITRACE_REACT_MARKER WITH_HITRACE_SYSTRACE

两类标记的用途可以这样区分:分析”某个阶段花了多久”(如 Bundle 加载、启动)时查看 RNOHMarker,分析”哪个函数耗时”(如挂载、触摸分发)时查看 TraceSection。两类标记的具体含义见 §3。

2.2 开启 trace 编译开关

两个编译宏默认关闭,需要手动开启:

编译宏 控制的 trace 类型 默认值
WITH_HITRACE_REACT_MARKER ReactMarker::* 异步 trace(阶段标记) OFF
WITH_HITRACE_SYSTRACE #RNOH::* 同步 trace(函数标记)+ JS 侧 nativeTrace* API OFF

    注意:开关需要在 CMake 引入 RNOH 目录(add_subdirectory)之前设置,才能对 RNOH 源码生效。

三种开启方式(任选其一,普通应用工程用方式 A 即可):

方式 A:应用 entry 的 CMakeLists.txt 中设置(应用工程推荐,模板默认已开启)

set(WITH_HITRACE_SYSTRACE ON)
set(WITH_HITRACE_REACT_MARKER ON)

方式 B:build-profile.json5 的 externalNativeOptions.arguments 中传递(不想改 CMakeLists.txt 时使用,配置随工程走)

"externalNativeOptions": {
  "arguments": [
  "-DWITH_HITRACE_SYSTRACE=ON",
  "-DWITH_HITRACE_REACT_MARKER=ON"
  ]
}

方式 C:第三方工程 CMakeLists.txt 中用 add_compile_definitions(三方库/HAR 场景)

set(WITH_HITRACE_SYSTRACE 1)
add_compile_definitions(WITH_HITRACE_SYSTRACE)
set(WITH_HITRACE_REACT_MARKER 1)
add_compile_definitions(WITH_HITRACE_REACT_MARKER)

2.3 抓取 trace

正式采集前,先固定测试条件:

          • 使用 release 构建和 release bundle,关闭 Metro、HMR、Inspector 和高频调试日志。

          • 固定设备型号、HarmonyOS 版本、RNOH 版本、刷新率策略、网络和测试数据。

          • 记录温度、是否充电、电量和电源模式;先预热,再重复相同操作至少 5 次。

          • 性能浮层只用于快速观察,正式测量时关闭浮层、录屏和其他可能改变负载的调试功能。

          • 同时保留基线数据与问题数据,避免只分析单次偶发 trace。

       使用 DevEco Profiler(深度录制)抓取 trace 的详细步骤(操作界面以 DevEco Studio 实际版本为准):

第 1 步:创建分析会话

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

2. 打开 DevEco Studio → Profiler,在设备列表中选择设备,在进程列表中选择要调测的应用(可以是正在运行的应用,也可以是已安装但未启动的应用);

3. 在新建任务区域选择场景模板,单击 Create Session。常用模板与适用问题:

模板 适用问题
Frame 卡顿丢帧:采集 App Frame、RS Frame 和渲染相关数据,适合滑动卡顿类问题
Time 函数执行效率:深度录制函数调用栈和每帧耗时,展现 ArkTS 到 Native 的跨语言调用栈,适合启动、时延类问题
Launch 启动耗时:分析启动周期各阶段耗时和核心线程运行情况

第 2 步:配置录制选项和泳道

          • 录制详情区域的工具栏上有多个配置图标,鼠标悬停可查看功能提示,按需添加录制选项;

          • 各泳道区域有下拉选项,可调整该泳道的录制功能;录制前可指定要录制的泳道。

       对 RNOH 应用,建议泳道至少包含:**App Frame、RS Frame、应用主线程、RNOH_JS、Render Service**。

第 3 步:启动录制并复现问题

单击任务窗口左上角(或左侧任务列表)的启动按钮,等待任务状态由 “initializing” 变为 “recording” 后,在设备上操作应用、复现性能问题。录制过程中不能再操作其他模板;如需更换模板,需结束本次录制后重新创建会话。

第 4 步:停止录制并等待解析

复现完成后单击停止按钮,泳道任务状态由 “analyzing” 变为 “rendering” 时解析结束,右侧详情区域显示分析结果;数据量较大时解析需要时间,请耐心等待。若 ArkTS Callstack、Callstack 等泳道显示 No Data,可将光标悬浮在泳道名称处的三角告警图标上查看原因。

第 5 步:保存 trace 文件

保存 .htrace 文件,便于后续查询和复核。

抓取完成后,即可在泳道中查看应用主线程、RNOH_JS 等线程的活动区间(同 §1.1 的示例):

更详细的 trace 抓取操作(包括命令行方式、采样配置、trace 文件解析等)请参考 HarmonyOS 官方文档Deep Recording 使用指导,其支持的全部场景模板(Snapshot、Allocation、ArkUI、Energy、ArkWeb、Network、Concurrency、GPU、CPU 等)也以该文档为准。

采集清单:每个问题建议至少提供 — 复现步骤和问题时间点、应用包类型和版本信息、DevEco Profiler trace 文件、App Frame/RS Frame/主线程/RNOH_JS 泳道数据、操作录屏或截图、基线版本与问题版本对照数据。

2.4 查看 trace 的技巧

       • 定位 JS 线程:在 trace 中搜索线程名 RNOH_JS。

       • 定位阶段标记:搜索 ReactMarker::,可快速定位到启动、Bundle 加载、Fabric 提交等阶段的起止。

       • 定位函数执行:搜索 #RNOH::,可定位到具体的函数调用区间(如挂载、触摸分发、TurboModule 调用)。

       • 定位异常窗口:先看 App Frame 和 RS Frame 泳道中的红色异常帧,然后缩放到该时间区间,查看该窗口内各线程的活动。

       • 确认线程身份:RNOH_MAIN 不在 trace 中直接显示此名称(主线程复用应用自身线程名),需通过应用主线程 + #RNOH::TaskExecutor::runTask 等 TraceSection 调用栈确认。

3. RNOH 关键 trace 点解读

3.1 ReactMarker(阶段级异步 trace)

React Marker 的作用是监听 RN 应用生命周期中的关键事件并打点,方便开发者分析性能问题。打点覆盖了从启动到渲染的完整时间线,可归纳为四类:

          • 启动初始化:运行时/实例/上下文创建、ArkUI模块注册;

          • Bundle 加载:下载、格式转换、分段注册、执行;

          • 页面内容显示:内容出现、Surface 前后台切换;

          • Fabric 渲染:提交、布局、diff、挂载。

       该类 trace 需要开启 WITH_HITRACE_REACT_MARKER 编译开关(见 §2.2)。

查看 ReactMarker 的 trace 时,需要注意标记名与 trace 显示名之间的映射关系:

          • 标记大多成对出现,带 _START/_STOP(或 _END)后缀,例如 RUN_JS_BUNDLE_START 和 RUN_JS_BUNDLE_STOP;

          • 成对的两个标记在 trace 中显示为同一个名称,如 ReactMarker::RUN_JS_BUNDLE(部分工具显示为 H:ReactMarker::RUN_JS_BUNDLE::Tag::<tag>),_START/_STOP 后缀不体现在名称中,而是分别对应该异步 trace 时间条的开始和结束;

          • 带 tag 的标记会在名称后追加 ::Tag::<tag>;

          • 个别标记名与 trace 显示名不同,如 JS_BUNDLE_STRING_CONVERT_START/STOP 在 trace 中显示为 ReactMarker::loadApplicationScript_StringConvert。

       以下是在 trace 工具中查看 ReactMarker 的示例:

图中红色框 1 代表的是应用进程,红色框 2 代表的是应用运行过程中打印出来的 React Marker 信息。

以下按阶段分组列出性能定界中最常用的 ReactMarker。在 trace 工具中搜索 ReactMarker:: 即可找到这些标记。

启动与运行时初始化

RNOHMarkerId trace 显示名 含义 可衡量 不可衡量
APP_STARTUP_START / APP_STARTUP_STOP ReactMarker::APP_STARTUP RNOH 应用启动总阶段 启动阶段的总耗时范围 各子阶段细节需看其他 marker
INIT_REACT_RUNTIME_START / INIT_REACT_RUNTIME_STOP ReactMarker::INIT_REACT_RUNTIME React 运行时初始化 运行时创建的起止时间 JS 引擎是否完全就绪
REACT_INSTANCE_INIT_START / REACT_INSTANCE_INIT_STOP ReactMarker::REACT_INSTANCE_INIT RNInstance 实例创建 实例创建的起止时间 实例创建后是否可立即渲染
CREATE_REACT_CONTEXT_START / CREATE_REACT_CONTEXT_STOP ReactMarker::CREATE_REACT_CONTEXT React 上下文创建 上下文创建耗时 Bundle 是否已加载

Bundle 加载

RNOHMarkerId trace 显示名 含义 可衡量 不可衡量
RUN_JS_BUNDLE_START / RUN_JS_BUNDLE_STOP ReactMarker::RUN_JS_BUNDLE JS Bundle 执行阶段 Bundle 从开始执行到执行完成的时间 Bundle 下载时间、业务页面首帧时间
JS_BUNDLE_STRING_CONVERT_START / JS_BUNDLE_STRING_CONVERT_STOP ReactMarker::loadApplicationScript_StringConvert Bundle 字符串转换 Bundle 数据格式转换耗时 Bundle 解析和执行时间
REGISTER_JS_SEGMENT_START / REGISTER_JS_SEGMENT_STOP ReactMarker::REGISTER_JS_SEGMENT JS 分段注册 多 Bundle 分段注册的耗时 分段注册后业务是否就绪
DOWNLOAD_START / DOWNLOAD_END ReactMarker::DOWNLOAD JS Bundle 下载阶段 Bundle 下载起止时间 下载后的缓存命中情况
REACT_BRIDGE_LOADING_START / REACT_BRIDGE_LOADING_END ReactMarker::REACT_BRIDGE_LOADING React 桥接模块加载阶段 整体加载阶段耗时 加载内部各步骤的细节

Native 模块

RNOHMarkerId trace 显示名 含义 可衡量 不可衡量
NATIVE_MODULE_SETUP_START / NATIVE_MODULE_SETUP_STOP ReactMarker::NATIVE_MODULE_SETUP ArkUI模块注册和初始化 所有ArkUI模块注册的总耗时 单个模块的初始化时间
INITIALIZE_MODULE_START / INITIALIZE_MODULE_END ReactMarker::INITIALIZE_MODULE 单个模块初始化 每个模块的初始化耗时 模块初始化后的首次调用时间
PROCESS_PACKAGES_START / PROCESS_PACKAGES_STOP ReactMarker::PROCESS_PACKAGES 处理所有 Package Package 处理的起止时间 每个 Package 内部的模块详情
CREATE_UI_MANAGER_MODULE_START / CREATE_UI_MANAGER_MODULE_END ReactMarker::CREATE_UI_MANAGER_MODULE 创建 UI_Manager 模块 UIManager 模块创建的起止时间 模块创建后的渲染表现
CREATE_MODULE_START / CREATE_MODULE_END ReactMarker::CREATE_MODULE 创建单个 React 模块 单个模块创建的耗时 模块创建后的调用情况
PROCESS_CORE_REACT_PACKAGE_START / PROCESS_CORE_REACT_PACKAGE_END ReactMarker::PROCESS_CORE_REACT_PACKAGE React Native 核心包加载 核心包处理的起止时间 核心包内部各模块的细节

Surface 生命周期

RNOHMarkerId trace 显示名 含义 可衡量 不可衡量
CONTENT_APPEARED ReactMarker::CONTENT_APPEARED 应用程序中的内容已成功渲染并显示给用户(根节点挂载到 ArkUI 内容容器 NodeContent) 根节点已挂载的时间点 像素已上屏、关键内容可见或可交互
ON_HOST_RESUME_START / ON_HOST_RESUME_END ReactMarker::ON_HOST_RESUME Surface 恢复(可见) Surface 进入可见状态的时间 Surface 首帧已渲染完成
ON_HOST_PAUSE_START / ON_HOST_PAUSE_END ReactMarker::ON_HOST_PAUSE Surface 暂停(不可见) Surface 进入不可见状态的时间 Surface 资源已释放

Fabric(UI 渲染)

RNOHMarkerId trace 显示名 含义 可衡量 不可衡量
FABRIC_COMMIT_START / FABRIC_COMMIT_END ReactMarker::FABRIC_COMMIT Fabric 渲染引擎的提交 React 一次提交的起止时间 提交后是否立即触发挂载
FABRIC_DIFF_START / FABRIC_DIFF_END ReactMarker::FABRIC_DIFF 新旧影子树对比(diff) diff 计算的耗时 diff 结果的 mutation 数量
FABRIC_LAYOUT_START / FABRIC_LAYOUT_END ReactMarker::FABRIC_LAYOUT 布局计算(Yoga) 布局计算的耗时 文本测量的内部细节
FABRIC_FINISH_TRANSACTION_START / FABRIC_FINISH_TRANSACTION_END ReactMarker::FABRIC_FINISH_TRANSACTION Fabric 事务完成 整个事务从提交到完成的耗时 挂载是否已在主线程执行完毕
FABRIC_BATCH_EXECUTION_START / FABRIC_BATCH_EXECUTION_END ReactMarker::FABRIC_BATCH_EXECUTION mutations 批量挂载执行 一批 mutations 在主线程执行的耗时 页面内容已完整渲染
FABRIC_UPDATE_UI_MAIN_THREAD_START / FABRIC_UPDATE_UI_MAIN_THREAD_END ReactMarker::FABRIC_UPDATE_UI_MAIN_THREAD 主线程同步更新组件属性 在主线程上同步更新单个组件属性的执行区间(避免跨线程延迟) 所有 Fabric transaction 的主线程挂载总时长

其他

RNOHMarkerId trace 显示名 含义 可衡量 不可衡量
CHANGE_THREAD_PRIORITY ReactMarker::CHANGE_THREAD_PRIORITY 线程 QoS 优先级变更 线程优先级调整的时间点和目标值 优先级变更后的实际调度效果

       提示:以上仅列出常用标记。完整的 56 个 ReactMarker 标记定义见源码 RNOH/Performance/RNOHMarker.h;其中部分标记(如 INIT_JS_RUNTIME、TURBO_MODULE_SETUP)不会输出到 trace,仅供 Marker 监听器(日志、性能指标等)使用。React Marker 的 trace 打点操作步骤,以及通过自定义 TurboModule 监听 React Marker 事件的实现方式,参见性能调优的 React Marker 介绍。

3.2 TraceSection(函数级同步 trace)

以下列出性能定界中最常用的 TraceSection 标记,在 trace 工具中搜索 #RNOH:: 即可找到。这些标记在 trace 中显示为连续的区间切片,可以直接看到函数的执行时长。

触摸与事件

trace 字符串 含义 所属线程
#RNOH::ArkUISurface::onTouchEvent::DOWN 触摸按下事件进入 RN Surface RNOH_MAIN
#RNOH::ArkUISurface::onTouchEvent::UP 触摸抬起事件进入 RN Surface RNOH_MAIN
#RNOH::ArkUISurface::onTouchEvent::MOVE 触摸移动事件进入 RN Surface RNOH_MAIN
#RNOH::Subject::notifyObservers 事件分发给观察者 各线程

挂载(Mount)

trace 字符串 含义 所属线程
#RNOH::MountingManager::doMount 开始执行一批 mutations 挂载 RNOH_MAIN
#RNOH::MountingManager::didMount <count> 挂载完成,count 为 mutation 数量 RNOH_MAIN
#RNOH::MountingManager::dispatchCommand 分发组件命令(如 scrollTo) RNOH_MAIN

Scheduler(Fabric 调度)

trace 字符串 含义 所属线程
#RNOH::SchedulerDelegate::performTransaction 处理一次 Fabric 事务 RNOH_MAIN
#RNOH::SchedulerDelegate::onNextFrame 下一帧调度 RNOH_MAIN
#RNOH::SchedulerDelegate::didMount Scheduler 挂载完成回调 RNOH_MAIN
#RNOH::SchedulerDelegate::handleMutation_parallel 并行处理 mutations RNOH_MAIN
#RNOH::SchedulerDelegate::parallelCreateWait 并行创建组件等待 RNOH_MAIN
#RNOH::SchedulerDelegate::otherCreate 串行创建组件 RNOH_MAIN

TaskExecutor(任务调度)

trace 字符串 含义 所属线程
#RNOH::TaskExecutor::runTask TaskExecutor 执行一个任务 各线程
#RNOH::TaskExecutor::runningTask 正在执行的任务(含任务 ID) 各线程
#RNOH::TaskRunner::task 事件循环执行单个任务 RNOH_MAIN / RNOH_JS

实例与通信

trace 字符串 含义 所属线程
#RNOH::RNInstanceCAPI::synchronouslyUpdateViewOnUIThread 主线程同步更新组件属性(避免跨线程延迟) RNOH_MAIN
#RNOH::MessageQueueThread::runOnQueue 向 JS 队列投递任务 RNOH_JS
#RNOH::RNInstanceInternal::callJSFunction 调用 JS 函数 RNOH_JS
#RNOH::ArkTSTurboModule::call (<name>::<methodName>) ArkTS TurboModule 调用(含模块名和方法名) RNOH_MAIN
#RNOH::TextMeasurer::measure 文本测量 RNOH_JS

       提示:以上仅列出常用标记。完整的 TraceSection 字符串可通过在源码中搜索 TraceSection s( 或 SystraceSection s( 获取。

3.3 按 trace 时间轴分析一次点击响应

单个 trace 切片只能说明某个环节的执行区间,定位性能问题时需要按时间顺序将多个切片串联,还原从输入到上屏的完整执行过程。

以一次点击响应为例,完整的 trace 点序列如下(两条竖线代表两个线程,从上到下即时间顺序):

依据各切片之间的时间间隔,可以定位耗时集中在哪个环节:

          • 如果 onTouchEvent::DOWN 到 runOnQueue 之间很长 → 主线程调度或触摸分发慢;

          • 如果 FABRIC_COMMIT 到 FABRIC_FINISH_TRANSACTION 之间很长 → JS 侧 React render/layout/diff 慢;

          • 如果 FABRIC_BATCH_EXECUTION 到 didMount 之间很长 → 主线程挂载慢;

          • 如果 didMount 到首个响应帧上屏之间很长 → ArkUI 布局/绘制或 Render Service 合成慢。

4. 定界方法与常见错误

4.1 责任域划分

一次有效定界至少给出以下结论之一:

责任域 典型证据 后续处理方
HarmonyOS 系统或 Render Service RS Frame 异常,应用按时提交;同设备多个应用同时劣化 系统/图形侧
ArkUI宿主或 ArkTS 业务 应用主线程被非 RNOH 的 ArkTS、I/O、图片处理等任务占满 ArkUI业务侧
RN JS 业务 RNOH_JS 长任务、频繁 React 提交或事件回调过重 RN 业务侧
三方 RN 组件 异常调用栈集中在三方组件,移除或降级后恢复 三方组件维护方
RNOH 框架 异常窗口关键路径落在 RNOH 实现,最小示例仍复现,且版本对比显示框架回归 RNOH 框架侧
证据不足 只有主观现象、平均 FPS 或单个长切片,无法覆盖从输入到上屏的完整过程 补充采集

“使用了 RNOH”不等于”RNOH 框架问题”。例如,大量业务组件一次性提交后,挂载耗时发生在 RNOH 主线程,但如果耗时随业务节点数增长且最小 RNOH 页面正常,通常应先归为 RN 业务负载或组件使用问题,而不是框架缺陷。

4.2 定界前准备

       • 确认 trace 编译开关已开启:检查 WITH_HITRACE_REACT_MARKER 和 WITH_HITRACE_SYSTRACE 是否在构建配置中设置为 ON(详见 §2.2)。

       • 测试条件的固定(release 构建、设备/版本/数据固定、预热重复、基线数据与问题数据等)见 §2.3 开头的采集要求,此处不再重复。

4.3 三类问题的快速判定

滑动卡顿丢帧

详细方法见RN性能调优之滑动卡顿问题介绍。

响应时延

按”输入锚点 → 首个响应帧上屏”分段测量,定位耗时最长的一段:

输入 → 应用触摸入口 慢 → 系统输入分发 / 主线程调度
触摸分发完成 → JS 事件 晚 → RNOH_JS 排队或阻塞
JS 提交 → 首个变化上屏 晚 → 挂载、ArkUI 或 RS
全程无任何视觉变化 → 先检查业务是否设计了即时反馈

完成时延

先定义”完成”终点,再分段定位责任方:

数据/网络未就绪 → 业务或服务侧
JS render/commit 晚 → RN JS 业务或三方组件
mutations 已产生但挂载晚 → RNOH_MAIN、节点规模或同步阻塞
应用按时提交但上屏晚 → ArkUI/RS/系统侧
仅 CONTENT_APPEARED 早 → 不能据此判定页面已经完成

详细方法见RN性能调优之点击响应和完成时延分析介绍。

4.4 常见错误定界

       • 只看 JS FPS/UI FPS 就分责:浮层是采样指标,适合发现趋势,不足以证明一次异常的关键路径。

       • 只看一个超过帧预算的函数:它可能不在异常帧、可能运行在非关键线程,也可能被后续流水线吸收。

       • 应用和 RS 同时红就判系统问题:应用晚提交也会向 RS 传导,必须看最早异常点。

       • RNOH 调用栈长就判框架问题:需要结合输入规模、最小示例和版本对照。

       • **把 CONTENT_APPEARED 当完成时延终点**:该标记早于实际上屏和业务内容完整。

       • 用 debug 数据和 release 基线比较:构建、日志、Inspector 和 bundle 差异会破坏对照条件。

       • 把平均值当全部结论:平均 FPS 会掩盖最大连续丢帧,平均时延会掩盖 P95/P99 尖峰。

相关文档

       • RN 性能调优之滑动卡顿问题介绍(系列第三篇)

       • 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. 目前还没评论,等你发挥!