跳到主要内容

React Native 在 Marketplace 中的性能

·阅读时长 6 分钟
Facebook 软件工程师

React Native 在 Facebook 系列的多个应用程序中被广泛使用,包括主 Facebook 应用程序中的顶级标签页。本文的重点是一个高度可见的产品,Marketplace。它在全球十几个国家/地区可用,使用户能够发现其他用户提供的产品和服务。

在 2017 年上半年,通过 Relay 团队、Marketplace 团队、移动 JS 平台团队和 React Native 团队的共同努力,我们将 Android 2010-11 年份设备上 Marketplace 的互动时间(TTI)缩短了一半。Facebook 历来将这些设备视为低端 Android 设备,它们在任何平台或设备类型上都具有最慢的 TTI。

典型的 React Native 启动过程如下

免责声明:比例不具代表性,并且会根据 React Native 的配置和使用方式而异。

我们首先初始化 React Native 核心(也称为“Bridge”),然后运行产品特定的 JavaScript,该 JavaScript 决定了 React Native 将在原生处理时间内渲染哪些原生视图。

不同的方法

我们早期犯的一个错误是让 Systrace 和 CTScan 来推动我们的性能优化工作。这些工具在 2016 年帮助我们发现了许多唾手可得的成果,但我们发现 Systrace 和 CTScan 都**不代表生产环境场景**,也无法模拟实际情况。在细分时间上的比例通常不正确,有时甚至大相径庭。在极端情况下,我们预期只需几毫秒的事情,实际上却需要数百或数千毫秒。尽管如此,CTScan 仍然有用,我们发现它能在三分之一的回归问题进入生产环境之前将其捕获。

在 Android 上,我们将这些工具的不足归因于:1) React Native 是一个多线程框架,2) Marketplace 与新闻源和其他顶级标签等大量复杂视图并存,3) 计算时间差异巨大。因此,本半期我们几乎所有的决策和优先级都由生产环境测量和细分数据驱动。

沿着生产环境检测之路

在表面上看,对生产环境进行检测可能听起来很简单,但事实证明这是一个相当复杂的过程。它需要多个迭代周期,每个周期 2-3 周;这是因为从提交代码到主分支,到将应用推送到 Play 商店,再到收集足够的生产样本以确保我们工作的信心,都存在延迟。每个迭代周期都涉及到发现我们的细分是否准确,它们是否具有正确的粒度,以及它们是否正确地加起来构成整个时间跨度。我们不能依赖 alpha 和 beta 版本,因为它们不代表普通用户。实质上,我们非常细致地基于数百万样本的聚合数据构建了一个非常准确的生产跟踪。

我们之所以一丝不苟地验证细分中的每一毫秒都正确地累加到其父指标,原因之一是我们很早就意识到我们的检测存在空白。结果发现,我们最初的细分并未考虑线程跳转引起的停顿。线程跳转本身并不昂贵,但跳转到已经在忙碌的线程则非常昂贵。我们最终通过在正确时机散布 Thread.sleep() 调用,在本地重现了这些阻塞,并通过以下方式成功修复了它们:

  1. 移除对 AsyncTask 的依赖,
  2. 取消在 UI 线程上强制初始化 ReactContext 和 NativeModules,以及
  3. 移除在初始化时测量 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 桥接器的成本降至接近零。