Marketplace 中的 React Native 性能
React Native 用于 Facebook 家族中多个地方的多个应用,包括 Facebook 主应用中的顶级标签。我们这篇文章的重点是一个高度可见的产品,Marketplace。它在十几个国家/地区可用,使用户能够发现其他用户提供的产品和服务。
在 2017 年上半年,通过 Relay 团队、Marketplace 团队、移动 JS 平台团队和 React Native 团队的共同努力,我们将 Marketplace 的交互时间 (TTI) 在 Android 上缩短了一半 2010-11 年级的设备。Facebook 从历史上将这些设备视为低端 Android 设备,它们在任何平台或设备类型上都具有最慢的 TTI。
典型的 React Native 启动过程如下所示
免责声明:比例并非代表实际情况,并且会根据 React Native 的配置和使用方式而有所不同。
我们首先初始化 React Native 核心(也称为“桥”),然后运行特定于产品的 JavaScript,该 JavaScript 确定 React Native 将在原生处理时间内呈现哪些原生视图。
不同的方法
我们早期犯的一个错误是让 Systrace 和 CTScan 驱动我们的性能工作。这些工具帮助我们在 2016 年找到了许多唾手可得的成果,但我们发现 Systrace 和 CTScan 并不能代表生产场景,也无法模拟实际情况下的发生情况。时间段内各个部分的比例通常不准确,有时甚至完全偏离目标。在极端情况下,我们预计需要几毫秒才能完成的事情实际上需要数百或数千毫秒。也就是说,CTScan 很有用,我们发现它在生产之前捕获了三分之一的回归。
在 Android 上,我们将这些工具的缺点归因于以下事实:1)React Native 是一个多线程框架,2)Marketplace 与许多复杂的视图(如 Newsfeed 和其他顶级标签)位于同一位置,以及 3)计算时间差异很大。因此,在本学期,我们让生产测量和分解几乎驱动了我们所有的决策和优先级。
生产检测之路
在表面上,对生产进行检测可能听起来很简单,但事实证明这是一个非常复杂的过程。它需要多个 2-3 周的迭代周期;由于在 master 中落地提交的延迟、将应用推送到 Play 商店以及收集足够的生产样本以对我们的工作充满信心。每个迭代周期都涉及发现我们的分解是否准确、它们是否具有正确的粒度级别以及它们是否正确地加总到整个时间跨度。我们不能依赖 alpha 和 beta 版本,因为它们不能代表普通用户。从本质上讲,我们非常细致地构建了一个非常准确的生产跟踪,该跟踪基于数百万个样本的聚合。
我们一丝不苟地验证每个分解中的毫秒是否正确地加总到其父指标的原因之一是,我们很早就意识到我们的检测存在差距。事实证明,我们最初的分解没有考虑由线程跳转引起的停顿。线程跳转本身并不昂贵,但线程跳转到已经执行工作的繁忙线程非常昂贵。我们最终通过在适当的时刻散布 Thread.sleep()
调用在本地复制了这些阻塞,并设法通过以下方法修复了它们:
- 删除我们对 AsyncTask 的依赖关系,
- 撤消在 UI 线程上强制初始化 ReactContext 和 NativeModules 的操作,以及
- 删除在初始化时测量 ReactRootView 的依赖关系。
总而言之,消除这些线程阻塞问题使启动时间缩短了 25% 以上。
生产指标也挑战了我们之前的一些假设。例如,我们过去曾在启动路径上预加载许多 JavaScript 模块,假设在一个捆绑包中共同定位模块将降低其初始化成本。但是,预加载和共同定位这些模块的成本远远超过了收益。通过重新配置我们的内联 require 黑名单并从启动路径中删除 JavaScript 模块,我们能够避免加载不必要的模块,例如 Relay Classic(当只需要 Relay Modern 时)。如今,我们的 RUN_JS_BUNDLE
分解速度提高了 75% 以上。
我们还在调查特定于产品的原生模块时发现了成果。例如,通过延迟注入原生模块的依赖项,我们将该原生模块的成本降低了 98%。通过消除 Marketplace 启动与其他产品的竞争,我们将启动时间缩短了等效的时间间隔。
最棒的是,许多这些改进广泛适用于使用 React Native 构建的所有屏幕。
结论
人们认为 React Native 启动性能问题是由 JavaScript 速度慢或网络时间过长引起的。虽然加快 JavaScript 等内容的速度会使 TTI 下降一个不小的数字,但这些内容对 TTI 的贡献比例都远低于之前的预期。
到目前为止,经验教训是测量、测量、测量!一些成果来自将运行时成本转移到构建时间,例如 Relay Modern 和 Lazy NativeModules。其他成果来自通过更智能地并行化代码或删除死代码来避免工作。还有一些成果来自对 React Native 进行大型架构更改,例如清理线程阻塞。性能没有万能的解决方案,而长期的性能提升将来自增量的检测和改进。不要让认知偏差影响您的决策。相反,要仔细收集和解释生产数据,以指导未来的工作。
未来计划
从长远来看,我们希望 Marketplace TTI 的性能能够与使用原生开发的类似产品相媲美,并且总体上让 React Native 的性能与原生性能相当。此外,虽然在本季度我们大幅降低了桥接启动成本约 80%,但我们计划通过 Prepack 等项目以及更多构建时处理来将 React Native 桥接的成本降低到接近零。