2

前不久,Facebook在ChainReact 2019大会上正式推出了新一代JavaScript执行引擎Hermes。
Hermes 是一款小巧轻便的 JavaScript 引擎,专门针对在 Android 上运行 React Native 进行了优化。对于许多应用程序,只需启用 Hermes 即可缩短启动时间、减少内存使用量并缩小应用程序大小,此外因为它采用 JavaScript 标准实现,所以很容易在 React Native 应用中集成。
在这里插入图片描述

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

可以发现,切换到Hermes后,加载时长,App大小和内存占有三个关键指标都有了显著的提高。
在这里插入图片描述
由于 Hermes 是针对移动应用优化的,因此我们没有计划将其集成到任何浏览器或 Node.js 等服务端基础架构中。在这些环境中现有的 JavaScript 引擎仍然是首选。

Hermes优化方案

在移动应用开发中,首次加载启动,内存大小和应用大小都是衡量应用好坏的重要指标,因此Hermes也是从这些方面还对React Native应用进行优化。

字节码预编译

通常来说,JavaScript 引擎会在加载后才解析 JavaScript 源代码并生成字节码,JavaScript 代码需要在生成字节码后才开始执行。为了跳过这一步,Hermes 引入了一个预编译器,在移动应用构建过程中运行。这样一来优化字节码的时间可以更长,使字节码更小、效率更高。现在还可以针对整个程序做优化,例如删除重复数据和打包字符串表等。

字节码的设计使其在运行时可以映射到内存中并解释,而无需急切地读取整个文件。许多中低端移动设备上性能较差的闪存 I/O 显著增加了延迟,因此按需从闪存加载体积经过优化的字节码会显著提升 TTI。此外,由于内存以只读方式映射并由文件支持,因此不使用虚拟内存的移动操作系统(如 Android)可以在内存不足时清除这些页面,进而减少了内存较少的设备上杀掉进程的现象。

在这里插入图片描述
尽管压缩后的字节码比压缩后的 JavaScript 源代码略大,但由于 Hermes 的原生代码体积较小,因此 Hermes 从整体上减少了 React Native 项目Android 应用的体积。

无 JIT

为了加快执行速度,流行的 JavaScript 引擎可以将频繁解释的代码编译为机器码,这项工作由即时(JIT)编译器执行。

Hermes 现在并没有 JIT 编译器。这意味着 Hermes 在某些基准测试中表现不会很出色,特别是那些依赖于 CPU 性能的基准测试。这一设计是有意为之:这些基准很难反映移动应用程序的实际工作负载。我们也对 JIT 做过一些实验,但我们认为想要获得真正的速度提升还是要关注上述现实指标。因为 JIT 必须在应用程序启动时预热,所以它们难以改善 TTI,甚至可能会损害 TTI。此外,JIT 会增加原生代码体积和内存消耗,这会对我们的主要指标产生负面影响。JIT 可能会拖累我们最关心的指标,因此我们选择不实现 JIT。

垃圾回收策略

在移动设备中内存的高效利用是非常重要的。一般来说,低端设备的内存往往是有限的,因此操作系统会强制杀掉使用过多内存的应用程序。当应用被杀后再次使用时需要缓慢地重启,后台功能也会受到影响。在早期测试中我们了解到,在 32 位设备上运行大型应用时虚拟地址(VA)空间,尤其是连续的 VA 空间都能是一种有限的资源,就算用了物理页面懒惰分配都没多大帮助。

因此,为了尽量优化引擎使用的内存和 VA 空间,我们构建了一个具有以下功能的垃圾回收器,主要的措施有:

  • 按需分配:仅在需要时以块的形式分配 VA 空间。
  • 非连续:VA 空间不必在单个内存范围内,这避免了 32 位设备上的资源限制。
  • 移动:能够移动对象意味着可以对内存进行碎片整理,并将不再需要的块返回给操作系统。
  • 分代:每次 GC 时不扫描整个 JavaScript 堆,减少 GC 时间。

集成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.js
This 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.hbc
This is Hermes Demo

在新工程中集成

目前 Hermes 是一个可选的 React Native 功能。如果要启用Hermes,需要确保 React Native项目的版本在0.60.2 以上,并且还需要对android/app/build.gradle 文件并进行以下更改。

project.ext.react = [
  entryFile: "index.js",
  enableHermes: true
]

如果应用已经至少构建了一次,请使用如下命令进行清理。

cd android && ./gradlew clean

然后,就可以正常开发和部署应用。

react-native run-android

在这里插入图片描述

调试

为了提供出色的调试体验,我们通过 DevTools 协议实现了对 Chrome 远程调试的支持。时至今日,React Native 还只支持在 Chrome 中运行应用的 JavaScript 代码时使用应用内代理调试。有了这种支持就能调试应用了,但 React Native 桥接器中不能同步原生调用。Hermes 对远程调试协议的支持允许开发者连接到在其设备上运行的 Hermes 引擎,并使用与生产中相同的引擎原生调试其应用程序。除了调试之外,我们还在考虑实现对 Chrome DevTools 协议的额外支持。
在这里插入图片描述

Hermes、JavaScriptCore和V8 的对比

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

首先,是原生 so文件的大小方面,RN所依赖的必要so库,Hermes比JavaScriptCore减少了约16%(单armeabi架构压缩后降低了0.5M左右),V8则要远大于Hermes和JavaScriptCore。
在这里插入图片描述
接下来,就是内存的波动情况,拿RNTester工程测试进入RN页面滑动进入若干页面并退出之后,内存的波动情况比较可以看到,V8和Hermes内存增长要更加平滑。
在这里插入图片描述
接下来是CPU波动情况,拿RNTester工程测试进入RN页面滑动进入若干页面并退出之后,对比CPU波动情况。Hermes明显好于V8和JavaScriptCore。,如下图所示。
在这里插入图片描述

Hermes存在的问题

相比JavaScriptCore来说,Hermes的确有很多的优点,但不是说一定好于JavaScriptCore,随着测试和集成的进行,Hermes带来的问题逐渐显现。

bytecode文件占用size过大

通过测试,Hermes编译的字节码文件比纯文本js文件增大100%。因此,打出的RN包就会比较大,并且动态下发RN增量包时,由于是二进制文件diff,差分效率也会降低。

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

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

执行纯文本js耗时长

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

持续优化

为了简化 Hermes 的迁移工作并继续在 iOS 上支持 JavaScriptCore,我们构建了 JSI;这是一种用于在 C++ 应用程序中嵌入 JavaScript 引擎的轻量级 API。此 API 使 React Native 工程师可以实现自己的基础架构改进。Fabric 就使用了 JSI,它可以抢占 React Native 呈现;TurboModules 也用了 JSI,它缩小了原生模块的体积,可以根据 React Native 应用程序的需要懒加载。

相关链接:
https://hermesengine.dev/
https://www.oschina.net/p/her...
https://www.infoq.cn/article/...


xiangzhihong
5.9k 声望15.3k 粉丝

著有《React Native移动开发实战》1,2,3、《Kotlin入门与实战》《Weex跨平台开发实战》、《Flutter跨平台开发与实战》1,2和《Android应用开发实战》