阅读 2123

Hermes Engine初探

Hermes引擎是什么,优势有哪些?

重要的事情提前说:Hermes引擎是Facebook研发,在React-Native Android端用于替换JavaScript Core的JavaScript引擎。Hermes引擎的优势是适合移动端的轻量级JavaScript引擎,使用aot编译,可以减少Android端内存使用,减小安装包大小,提升执行效率。

什么是JavaScript引擎?

JavaScript引擎是一个专门处理JavaScript脚本的虚拟机,一般会附带在网页浏览器之中。

主流JavaScript引擎

V8(Google)、JavaScriptCore(Apple)、SpiderMonkey(Firefox)

RN中的JavaScript引擎

Weex,Android:V8,iOS:JavaScriptCore

RN,Android:JavaScriptCore(Hermes、V8),iOS:JavaScriptCore(Apple要求)

:Hermes Engine在React-native 0.60.2 版本后支持

Hermes的特色

  • 预编译字节码(引擎加载二进制代码效率高于运行JS脚本)
  • 无JIT编译器(减小了引擎大小,优化内存占用,但直接运行JS脚本的性能差于V8和JSC)
  • 针对移动端的垃圾回收策略

优化原理

(截取自code.fb.com)

传统JavaScript引擎通常是以上图的模式完成代码执行的,编译阶段只完成babel转义和minify压缩,产物还是JavaScript脚本,解释与执行的任务都需要在运行时完成(如V8引擎,还会在运行时将JavaScript编译为本地机器码)很明显缺点就是在运行时需要边解释边执行,甚至需要占用系统资源执行编译任务。

image-20190729172843337

(截取自code.fb.com)

Hermes引擎使用了aot编译的方式,将解释和编译过程前置到编译阶段,运行时只完成机器码的执行,大大提高了运行效率。

已有项目接入Hermes

  1. 升级React-Native及相关库升级(成本较小)
  2. 因为React-Native 0.60.x变更为依赖AndroidX,所以Android项目需要使用28以上版本编译,适配Android高版本,且需要迁移到AndroidX(成本较大)
  3. 修改build.gradle,添加Hermes相关属性及依赖(成本较小)

是否支持CodePush?

Hermes引擎预编译后的产物与RN原方式相同,都是在assets文件夹下生成的index.android.bundle文件。RN原方式中index.android.bundle是经过压缩的JavaScript脚本文件,Hermes预编译后则是二进制文件。因为只有产物文件格式的区别,并没有修改原有JS Bundle的加载方式,所以CodePush可以继续使用。

目前code-push的两种发布模式支持情况:

发布方式 是否支持 备注
code-push release-react 支持,但无法产生预编译脚本产物 需依赖react-native bundle命令完成脚本打包,该命令尚不支持预编译
code-push release 支持

调试效率

Debug模式下Hermes不开启预编译以支持Hot Reload,缺点是Release模式下所有Hermes引擎优势都不存在,甚至因为无JIT导致性能还要差于原有引擎。但开发者模式并不追求性能,而更追求调试效率。

Debug模式内置libhermes-inspector.so,支持Chrome inspect的使用,支持DevTools协议,比原有RN调试体验更佳(应用内代理,不能同步调试原生调用)

ES标准支持

Hermes支持ES6,紧跟最新的JavaScript规范。为了优化引擎大小,不支持RN程序中使用较少的语言特性,如本地eval()。

性能调研

包大小分析

image-20190729174643389

(JSC引擎Release包)

image-20190729174736815

(Hermes引擎Release包)

原包大小20MB(JSC)

新包大小18MB(Hermes)

包大小减小2MB,整体减少2MB / 20MB = 10%

分析具体包大小减小的原因可以发现,包内容两者只有lib大小和assets的大小存在差异。

image-20190729174952340

(JSC引擎Release包)

image-20190729175007916

(Hermes引擎Release包)

对比lib内容,发现大小差距主要是由libjsc.so和libhermes.so两者的差距导致的,即Hermes引擎的大小。

image-20190729175354246

(JSC引擎Release包)

image-20190729175504457

(Hermes引擎Release包)

对比assets内容,发现大小变化主要由index.android.bundle,即JavaScript打包产物引起,Hermes模式下反而更大的原因是进一步编译为二进制代码。

两者影响叠加导致整体减小,包大小得到优化。(支持的平台越多,包体积优化效果越好)

内存分析

实验方法:在相同的业务页面稳定状态下通过Memory Profiler查看内存占用情况

image-20190729150455599

(JSC引擎Release包)

image-20190729150430492

(Hermes引擎Release包)

原包平均内存占用210MB

新包平均内存占用190MB

内存占用平均减小20MB以上,整体减小20MB / 210MB = 10%

分析Profiler数据可以发现,内存优化主要发生在Code内存区。

image-20190729180820821

(JSC引擎Release包)

image-20190729180739358

(Hermes引擎Release包)

Google官方文档中对内存Code区的描述

Code:您的应用用于处理代码和资源(如 dex 字节码、已优化或已编译的 dex 码、.so 库和字体)的内存。

联系到上个章节中包大小分析中libhermes.so尺寸的减小,可以很容易想到,内存占用的减少就是因为.so对内存占用的减小。另外两者对JavaScript内存的占用也有细微差别,但是可以忽略不计。

TTI性能

TTI:Time to Interactive,用户可交互时间,启动到页面渲染完成并且可以正常响应用户的输入的时间,衡量用户体验的移动端指标

React-Native Android中主要是Application onCreate开始到RN组件渲染完成可交互的时间。

值得吐槽的是,在iOS版本的Pref Monitor中直接就包含了这个指标的显示,但是Android版本的Pref Monitor只有四个指标,且并没有TTI这一指标。

在Android平台上可以通过RN提供的ReactFindViewUtil类获取RN组件对应的原生组件,注册对应的渲染回调,在控件渲染完成时记录TTI结束时间。

image-20190729222910281

(JSC引擎Release包)

image-20190729222550135

(Hermes引擎Release包)

原包TTI 829ms

新包TTI 694ms

TTI减少135ms,整体减少135ms / 829ms = 16%

总结

面对Flutter的咄咄攻势,React-Native终于做出了一些改变,Hermes作为一款适合移动端的JavaScript引擎,确实有其性能优势,希望通过本文能够让你更加了解Hermes。

文章分类
前端
文章标签