写点什么

加载速度提升 15%,携程对 RN 新一代 JS 引擎 Hermes 的调研

  • 2019-09-05
  • 本文字数:4669 字

    阅读完需:约 15 分钟

加载速度提升15%,携程对RN新一代JS引擎Hermes的调研

引言

Facebook 在 ChainReact2019 大会上正式推出了新一代 JavaScript 执行引擎 Hermes。Hermes 是个轻量级的 JS 引擎,专门对 Android 上运行 ReactNative 进行了优化。我们第一时间在 CRN 项目中集成了 Hermes, 并做了深度调研。

一、Hermes 介绍

自 ReactNative 推出以来,有大量的 APP 接入并使用,其中也包括大型应用的主流程业务。随着业务复杂度不断上升,性能问题变得无法忽视。


在分析性能数据时,Facebook 团队发现 JavaScript 引擎是影响启动性能和应用包体积的重要因素。由于 JavaScriptCore 最初是为桌面浏览器端设计,相较于桌面端,移动端能力有太多的限制,为了能从底层对移动端进行性能优化,Facebook 团队选择自建 JavaScrip 引擎,设计了 Hermes,限于 iOS AppStore 审核限制,目前仅用于 Android 平台。


Chain React 大会上官方给出了 Hermes 引擎一组数据:


  • 从页面启动到用户可操作的时间长短(Time To Interact:TTI),从 4.3s 减少到 2.01s

  • App 的下载大小,从 41MB 减少到 22MB

  • 内存占用,从 185MB 减少到 136MB


CRN 先前做过框架代码拆分和预加载、业务代码懒加载、业务代码预加载等性能优化方案,正困惑于如何更近一步进行性能优化。当看到 Hermes 这三个关键指标都有了显著的提高,非常激动,觉得 Hermes 是非常好的一个方向,接下来我们就来了解 Hermes 的使用和实测性能数据。

二、快速上手 Hermes

Faceback 团队已经将 Hermes 工具上传到了 npm : hermesvm。hemres 工具可以直接运行 JS 代码、转换字节码并且提供非常多的参数进行调优控制。


这里介绍一下 hermesvm 执行 JS 代码和转换 bytecode 功能。



// 创建hermes_test文件,内容:print("This is Hermes Demo");vim hermes_test.js
// 直接执行纯文本js~/node_modules/hermesvm/osx-bin/hermes hermes_test.jsThis is Hermes Demo
// 转换成bytecode~/node_modules/hermesvm/osx-bin/hermes --emit-binary hermes_test.js -out hermes_test.hbc
// 执行字节码~/node_modules/hermesvm/osx-bin/hermes hermes_test.hbcThis is Hermes Demo
复制代码

三、Hermes 是如何优化的?

主流 JavaScript 引擎,例如 JSC、V8、SpiderMonkey 等几乎都是为了桌面端浏览器服务的,Hermes 针对移动终端设备的特点做了一些优化,其中最重要的我们认为是以下两点:

3.1 字节码预编译

现代主流的 JavaScript 引擎在执行一段 js 代码的大概流程是:


  • 先读取源码文件

  • 解析源代码并转换成字节码(bytecode)

  • 最后执行


在运行时解析源码转换字节码是一种时间浪费,所以 Hermes 选择预编译的方式在编译期间生成字节码。这样做一方面避免了不必要的转换时间,另一方面多出的时间可以用来优化字节码,从而提高执行效率。


3.2 放弃 JIT

为了加快执行效率,现在主流的 JavaScript 引擎都会使用一个 JIT 编译器在运行时通过转换成机器码的方式优化 JS 代码。Faceback 团队认为 JIT 编译器有主要俩个问题:


  • 要在启动时候预热,对启动时间有影响;

  • 会增加引擎 size 大小和运行时内存消耗;


基于这俩点对性能指标的影响,Faceback 团队决定不实现 JIT 编译器。


这里所谓放弃 JIT,有两点需要再解释一下:


  • 纯文本 JS 代码执行效率降低。放弃 JIT,是指放弃运行时 Hermes 引擎对纯文本 JS 代码的编译优化。我们的验证数据也表面,纯文本的 JS 代码执行,Hermes 引擎明显比 JavaScriptCore 慢。

  • 对 RN 代码的动态性无影响。由于 Hermes 仍然可以执行纯文本的 JS 代码,并且可以支持动态读取 bytecode, 因此对 RN 的动态性并无影响。

四、如何集成 Hermes?

4.1 从新创建工程集成

1. 升级最新react-native-clinpm install -g react-native-cli
2.初始化最新react-native工程,最新版为0.60.3react-native init HermesDemo
3. 开启hermes, 编辑HermesDemo工程 android/app/build.gradl文件 project.ext.react = [ entryFile: "index.js",- enableHermes: false // clean and rebuild if changing+ enableHermes: true // clean and rebuild if changing ]
4. 使用Relase包体验Hermes带来的速度提升react-native run-android --variant release
复制代码

4.2 从源码集成

git clone https://github.com/facebook/react-native.git // 需要切换到Hermes release节点,比如:eec4dc6cd react-nativenpm install./gradlew :RNTester:android:app:installHermesRelease // 使用生产环境hermes
复制代码

4.3 Hermes 集成过程分析

分析 react-native react.gradle 源码可以看到,如果打开了 Hermes 开关,会在原先打包 RN 代码的 bundleXXXJsAndAsset task 后面追加执行一段 Hermes 转换命令: hermes --emit-binary -out xxx。


...// 1. 执行标准RN打包commandLine(*nodeExecutableAndArgs, cliPath, bundleCommand, "--platform", "android", "--dev", "${devEnabled}",                    "--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir,                    "--sourcemap-output", jsPackagerSourceMapFile, *extraArgs)......// 2. 将打包好的jsbundle文件转换成字节码if (enableHermes) {    commandLine(getHermesCommand(), "-emit-binary", "-out", jsBundleFile, jsBundleFile, *hermesFlags)}...
复制代码

4.4 执行过程分析

为了进一步抽象 JavaScript 执行层,RN 底层创建了 JSExecutor 和 Runtime 接口,并把大部分业务逻辑放到了实现了 JSExecutor 的 JSIExcutor.cpp 中。对于 JavaScript 执行引擎来说只需要实现 Runtime 接口即可对接 RN 框架。


JavaScriptCore 的 Runtime 实现类是 JSCRuntime。相应的,此次 Hermes 升级,底层创建了 HermesRuntime。


// JSCRuntime.cpp jsc Runtimeclass JSCRuntime : public jsi::Runtime
// hermes.h hermes Runtimeclass HermesRuntime : public jsi::Runtime...
复制代码


每一种 JSExecutor 都提供了创建类 XXXExecutorFactory 来创建相应实例,并且提供了相应的 Java 对象。


RN 框架在初始化 ReactInstanceManager 的时候需要传入 JavaScriptExecutorFactory。如果要切换 JavaScript 执行引擎只需要在 ReactInstanceManager 创建的时候做控制即可。


官方的控制流程是,优先加载 jscexecutorso,如果成功则使用 JSCRuntime,否则使用 HermesRuntime。


private JavaScriptExecutorFactory getDefaultJSExecutorFactory(String appName, String deviceName) {    try {      // If JSC is included, use it as normal      SoLoader.loadLibrary("jscexecutor");      return new JSCExecutorFactory(appName, deviceName);    } catch(UnsatisfiedLinkError jscE) {      // Otherwise use Hermes      return new HermesExecutorFactory();    }  }
复制代码


由此可见无论是对于 RN JS 代码的打包还是 Native 代码逻辑的更改,升级 Hermes 的成本都非常低。

五、Hermes,JavaScriptCore,V8 的对比

通过上面的 Hermes 集成分析可知,Hermes 对整个 RN 原有架构的侵入是极少的,甚至做到了可插拔式接入。我们很快将 Hermes 集成到携程 CRN 框架,并和原先的 JavaScriptCore 引擎以及社区提供的 V8 引擎做了比较。


经过我们的数据验证,Faceback 团队提出的关键性指标相较于原先的 JSC 都有了显著提高。


  • 首屏渲染速度:bytecode 代码执行情况下,Hermes 比 JavaScriptCore 要快。在携程 App 中,拿门票业务做了验证,在做了预加载的情况下,首屏加载速度依然可以提升约 15%。而 V8 的表现就非常糟糕了。

  • Native so size:RN 所依赖的必要 so 库,Hermes 比 JavaScriptCore 减少了约 16%(单 armeabi 架构压缩后降低了 0.5M 左右),V8 则要远大于 Hermes 和 JavaScriptCore。



  • 内存:拿RNTester工程测试进入 RN 页面滑动进入若干页面并退出之后,内存的波动情况比较可以看到,V8 和 Hermes 内存增长要更加平滑。



  • CPU:拿RNTester工程测试进入 RN 页面滑动进入若干页面并退出之后,对比 CPU 波动情况。Hermes 明显好于 V8 和 JavaScriptCore。


六、Hermes 引擎的动态性

另外通过我们的测试,Hermes 在执行字节码和文本 JS 上有一些很有意思的特性,这些特性让升级成本变得非常低:


  • Hermes 支持执行纯文本的 js

  • 支持动态加载纯文本 js 或者 bytecode

  • 支持 bytecode 和纯文本 js 混合使用:比如 a.hbc 是 bytecode,模块中引用了 b.js,b 模块是纯文本 js。在加载的时候可以先加载 a.hbc 文件,然后加载 b.js 文件。可正常执行。

七、Hermes 目前的问题

Hermes 诸多优点让我们团队非常兴奋,几乎觉得应该立马把 JavaScriptCore 下掉,更换至 Hermes。但随着测试和集成的进行,Hermes 带来的问题逐渐显现。

7.1 bytecode 文件占用 size 过大问题

Hermes 编译的字节码文件比纯文本 js 文件增大 100%。


携程旅行 App 的安装包中有 20MB(7z 压缩后)左右的 RN 业务代码,如果都编译成 bytecode,将会再增加 20MB 大小,这是无法接受的。另外,动态下发 RN 增量包时,由于是二进制文件 diff,差分效率极低。


为了解决这个问题,我们根据 Hermes 的特性,转变思路,将 Hermes 的 bytecode 编译放到客户端去做,客户端同时存储 js 和 bytecode 文件,如果有 bytecode 编译完成则使用 Hermes,否则仍然使用 JavaScriptCore。


Hermes 开源项目提供了编译 bytecode 的 complieJS 方法,但这部分代码没有默认打包到 RN 的 Hermes 引擎中,我们稍加整合、封装,通过 JNI 暴露出来,供业务使用。


拿最大的 RN 业务包(1100 个文件,6.5MB 大小),做测试,后台线程执行,小米 9 Android10 耗时 2.49 秒;三星 S6edge+ android 7.0 耗时 6 秒。由于 bytecode 不是必须,因此该耗时尚可接受。

7.2 执行纯文本 js 耗时长

在客户端将纯文本 js 转换成 bytecode 之前,我们让 Hermes 加载纯文本。但实际测试下来,发现 Hermes 加载纯文本的性能比 JavaScriptCore 要慢将近 30%。主要原因是 Hermes 删除 JIT 功能,致使对纯文本 js 代码运行变慢。

7.3 缓存问题

我们对原生 RN 框架做了大量的优化,缓存使用过的 JS 执行引擎是优化过程非常重要的一环。


拿门票页面举例来说,如果用户启动 App,第一次进入门票业务将会使用一个全新的 JavaScript 引擎并从磁盘读取文件、加载文件、执行 JS 代码。用户退出门票页面之后该引擎被缓存,如果用户再一次进入将会使用缓存的引擎,不用重新读取、加载和执行,仅仅需要创建相关 JS 对象并渲染即可。


遗憾的是,测试 Hermes 的缓存的时候,我们发现使用缓存的 Hermes 引擎加载业务代码表现非常一般,甚至某些情况下比第一次加载还要慢。而使用缓存的 JavaScriptCore 引擎,第二次打开页面的速度与打开纯 native 页面的速度几乎相当,并且表现相当稳定。



为什么使用缓存的 Hermes 引擎打开页面速度不理想,可能和 Hermes 的设计有关,我们还在进一步分析中。

八、总结与展望

  • 从目前情况来看,在解决缓存问题之前,我们无法在线上版本直接引入 Hermes。

  • 解决缓存问题之后,可以采用 JavaScriptCore+Hermes 双引擎。通过客户端转换 bytecode 字节码。使用 jsc 加载优化之前的纯文本 js,一旦优化完毕切换至 Hermes 引擎。

  • 另外如果使用 Hermes 引擎我们需要充分测试稳定性和兼容性。

  • Hermes 通过预编译字节码的方式提升 js 执行速度,给了我们新的思路。我们也正在调研 JavaScriptCore 或者 V8 的 bytecode 在移动端的支持度,性能和兼容性。


作者介绍


储贻锋,携程无线平台研发部基础框架组资深 Android 研发,目前主要负责 CRN Android 端和携程 Android 基础架构的维护与开发工作。


本文转载自公众号携程技术中心(ID:ctriptech)


原文链接


https://mp.weixin.qq.com/s/BOeuLoZjCdi61P_MhaJT0g


2019-09-05 08:004077

评论 1 条评论

发布
用户头像
峰哥牛啊
2021-05-19 17:43
回复
没有更多了
发现更多内容

官方线索|科大讯飞全球1024开发者节

xcbeyond

1024我在现场

人工智能解决方案 --- 智能运维(AIOps)

micklongen

人工智能 AIOPS 知识图谱 智能运维 数据工程

【设计模式】第四篇 - 简单工厂

Brave

设计模式 工厂模式 10月月更

Linux system hardening: adding hidepid to /proc mount point

卓丁

Linux linux security proc hidepid

WEB图像优化

devpoint

性能优化 image 图像格式 10月月更

趣说 Node.js 的事件循环

Regan Yue

node.js Regan Yue 10月月更

未来已来,运营商如何驱动区块链应用创新提速?

CECBC

博鳌亚洲论坛国际科技与创新论坛第二届大会区块链分论坛紧密筹备中

时空云

区块链 博鳌 亚洲论坛

Minio环境搭建

飞鸟

Minio 分布式文件存储

【Flutter 专题】133 图解自定义 ACEWaterButton 水波纹按钮

阿策小和尚

Flutter 小菜 0 基础学习 Flutter Android 小菜鸟 10月月更

👊 【Spring技术特性】带你看看那些可能你还不知道的特性技巧哦!

洛神灬殇

Java spring Spring特性 10月月更

在线字符串哈希/散列工

入门小站

工具

CPU、指令集、微架构概念学习

轻口味

10月月更

Spring 框架学习

风翱

spring 10月月更

linux【redhat&ubuntu】下ffmpeg-3.1安装编译及视频转码

程序员架构进阶

架构 ffmpeg 视频流 10月月更

018云原生之基础架构

穿过生命散发芬芳

云原生 10月月更

【LeetCode】环形链表Java题解

Albert

算法 LeetCode 10月月更

初始化 Ubuntu 工作环境

看山

ubuntu 10月月更

API网关Kong实战

飞鸟

kong api 网关 API Gateway

5分钟搞懂Monorepo

俞凡

git 架构

微博评论高性能高可用计算架构设计

看,有只猪

【产品】论增长黑客思维如何让B端产品爆发式增长

极光一号。

云原生 用户增长 b端产品经理 增长黑客

Gas 机制是如何运作的

Rayjun

以太坊

分布式文件存储系统Minio实战

飞鸟

Minio 分布式文件存储

linux之awk使用技巧

入门小站

Linux

Go 扇入 / 扇出

baiyutang

golang 10月月更

【架构实战营作业】模块五——微博评论计算架构

聆息

区块链将规则写入代码 重构市场新制度

CECBC

Prometheus 基础查询(四)修饰符

耳东@Erdong

Prometheus 10月月更

Kafka 生产环境部署指南

Se7en

Win11安装PyTorch

IT蜗壳-Tango

10月月更

加载速度提升15%,携程对RN新一代JS引擎Hermes的调研_技术管理_储贻锋_InfoQ精选文章