阅读 2720

直击痛点React Native性能监控评估方案

原文链接: mp.weixin.qq.com

姜萌

2014年加入去哪儿网,在机票事业部/用户产品从事原生客户端和基于 ReactNative 的泛前端研发工作,专注于构建可提供快速交付和迭代能力的技术方案,以及应用架构演进和周边性能质量设施建设。

背景

近几年 React Native 技术栈的普及增速快,但其生态尚处于早期阶段,在架构、性能质量、周边设施上仍有很多优化、填充和演进的空间。尤其在性能监控评估方面以往针对原生、 web 的方式方法在 React Native 上不适用,目前 Qunar 大客户端机票团队已经规模化使用 React Native ,在收获跨平台交付效率、快速迭代热发布能力的同时我们希望能规范化应用的性能水平,具备性能自动化监控评估的 APM 设施。

解决什么问题

回到这个方案的初衷,是要解决这几个方面的问题:

性能应用水难以量化并导致调优工作滞后

客户端怎么样算是性能好性能坏,在通常是没有一个明确的界定方式的,事实上客户端的性能调优工作多数是被动滞后,开发阶段大家用几个手机点一点觉得流畅就是流畅,但其他真实用户或是对体验有较高要求的群体确实会有不一样的感受结论。

RD 对性能认知不足

即便生态发展迅速, React Native 确实是一项非官方生态的新技术,这个生态所积累的深度学习资源和最佳实践经验不足够丰富,部分开发者对其性能方面的认知还不足。

怎么办 -- 设计目标与实现拆解

针对开发和项目过程痛点,我们期望的目标如下:

  • 项目性能评估客观可量化

  • 自动化侦测性能缺陷

  • 问题定位辅助决策

方案拆解思路:首先是找到针对 React Native 的性能相关性数据,也就是当性能出现下滑时,可以通过哪些维度的数据表现出来;然后有了数据样本需要进行记录;之后时对瞬时和一段周期内的样本进行自动化分析;最后时提供结果反馈。

解决方案

首先是性能相关性数据的实时采样模块,包括 MRT (消息响应及时性)、 GCP (绘图指令生效推迟)、逻辑同步帧率等。 其次是对样本的记录,记录模块目前支持两种记录模式,一种是存储在手机本地,一种是提供外放协议可以把数据投递到外部对接系统。 之后是实时分析,基于记录的各个维度数据进行缺陷侦测并生成预警。通过输出模块输出到开发者日志和可视化报表,这里我们后期有计划自动生成对应项目的性能 bug 对接到 QA 系统。

如何采集相关性数据、分析规则与调优策略

在行业缺乏相关方案的背景下,最难地方在于寻找 React Native 应用的性能相关性数据都是什么、在哪里,围绕 RN 的实现原理我们挖掘到了这些维度:

  • MRT (消息相响应及时性)

  • GCP (绘图指令延迟)

  • 无 SCU 优化、冗余 render 调用侦测

  • 绘图帧率与逻辑同步帧率

  • 关键线程 CPU 负载

  • 内存用量

  • 流量消耗

下面着重讲解几个代表性的性能相关性数据自动化采集分析和对应的调优策略:

MRT (消息响应及时性)

抛开具体实现, React Native 中 Native 域与 JS 域互操作的最简化模型如下,两边各有一个基于消息队列模型的线程, js 侧这个线程就是 javascript 代码执行的线程, native 侧是 nativemodulethread 用来执行 js 测投递过来的 NativeModule 调用。两侧的互操作是基于 jni 向对方投递消息,不同于大多数原生开发的单域仅是内存地址对应的代码块,因为涉及到线程切换、反射、任务队列、双向异步,这个过程并没有那么及时,尤其涉及到视图频繁变更、手势事件传递这些场景下相对原生方案延迟还是蛮大的。

MRT 侦测、预警

我们的 APM 支持对这个指标进行实时采样,并针对异常峰值进行自动化预警反馈。这样 RD 在开发阶段可以及时感知到诸“如按钮点击了 onPress 没有调用“、“ js 函数执行了但没有在 native 上生效的“这些在过去比较隐蔽的性能缺陷。

MRT 优化策略

  • 减少 native/js 互操作频次,比如移到单域处理(比如 Animation 的 useNativeDriver 的设计符合这一原则);

  • 减少 nativemodulethread 任务堆积。比如提高算法效率、利用多核 CPU 设计并行计算算法、使用异步来优化调用等待链;

  • 减少 js_thread 上任务堆积。比如单线程模型上对大任务做拆解、优化调度顺序。

附 React Native 中 Native 与 JS 互操作原理图:

侦测无 SCU 优化的组件和计算

SCU 优化(shouldComponentUpdate optimization)是在 reactjs/react native 中经常强调的一个调优项。我们通过 hook 组件的 componentDidUpdate 对先后的 props 和 state 做 deepdiff ,根据结果分为深比较值不等、深比较仅函数不等、浅比较为开发者提供优化建议。在反馈报表中可以清晰感知到哪些组件发生了冗余的 render 以及优化收益预估。

防止冗余 render 调用调优策略

对于使用 Redux 管理数据事件流的项目
  • store 使用不可变数据结构

  • 根据组件需要的最小化依赖按需定制 mapStateToProps

  • 使用 PureComponent 或 redux connect、qrn-reduxPlugin 赋予浅比较规则

  • 避免单个 render 函数/stateless function 生成深度和广度较大的 JSX Tree 

对于使用 MobX 的项目

MobX 的自动化依赖收集机制在数据变更后会定向更新最小粒度的组件,且 observer 组件的 SCU 函数已经被替换无需考虑 SCU ,非 observer 组件使用 PureComponent 并遵循性能编程规范即可。

渲染帧率与逻辑帧率

样本采集

React Native 的实际渲染执行和视图属性计算声明是彼此异步进行,以往针对原生应用的帧率监控指标在 RN 应用下会非常“好看”,几乎不可能出现 ANR ,极少会掉帧。但事实是 js 域的每次 render 执行并不是实时生效到实际界面上, js 域上的视图数据计算和属性配置成为瓶颈。以安卓为例我们通过 VSYNC 特性在每两帧间检查是否发生 JSInstance.onBatchCompleted 视图操作事务提交以及是否期间完成了对 BatchedOperation Queue 的执行来判定 native 上每次渲染是否真的生效了来自 js 域提交的视图操作。

对应的针对渲染帧率与逻辑帧率的自动化分析规则

持续性弱体验侦测:

  • 渲染帧率或逻辑帧率连续 n 次连续掉帧

  • 过去5 s 内渲染帧率或逻辑帧率的 P80分位数是否>30fps

潜在缺陷侦测:

  • 瞬时帧率异常侦测(渲染帧率和逻辑帧率)

执行过程限时超标侦测

通常在设计上我们会对一些模块的初始化、数据处理和计算的关键体验环节设定执行耗时的限制,通过构建 AOP 样式的 decorator ,对同步函数和基于 Promise/async/await 协程的异步函数进行 trace 并统计是否实际执行耗时不符合设计要求的环节。

  1. @tracePerf({ expectedTimeCost: 200, strict: true })

  2. async function invokeHeavyThingsXXX() {

  3.    const result = await hotdog.invoke(...);

  4.    await parallel.compute(result);

  5. }

复制代码

其他周边: CPU 关键线程负载、内存用量、应用流量用量监测

关键线程(JS 线程、native_module 线程、UI 线程)的持续性高负载侦测;

内存用量: vm heap 用量、native heap 用量、低内存预警 ;

流量:流量消耗飙升侦测。

目前 Profiler 支持的自动化分析预警规则总结

可视化输出反馈

开发者可以实时观测性能评估过程的各项指标表现,并从中收集反馈信息和优化建议指引,将性能缺陷的挖掘和调优前置到开发和测试阶段。

适用范围、如何接入

适用于所有基于 React Native 及其衍生方案如 QRN 的应用,只需配置依赖, SDK 化使用。

对 APM 未来的思考

  • 开放式架构:将流程、设计约定、基础功能和算法抽离为轻便的 APM Core ,把具体的 sampler 和 profiler 和 output 作为其上的插件实现进行剥离,各业务线团队和开发者根据自身需要定制、扩展性能维度和分析规则。

  • 功能延展:扩展 Output 模块和应用范围,对性能评级标准化并对接 QA 流程。

  • 自身性能优化:部分 js 代码移交到 native 去实现,降低 APM 本身对 js 域单线程的负载损耗。