ReactNative在游戏营销场景中的实践和探索-Hermes引擎

avatar
@字节跳动

作者:字节游戏中台客户端团队 - 熊文源

客户端跨端框架已经发展了很多年了,最近比较流行的小程序、Flutter、ReactNative,都算是比较成功、成熟的框架,面向的开发者也不一样,很多大型App都广泛的使用了,笔者有幸很早就参与学习使用了这些优秀的跨端方案,在这几年的开发和架构设计中,除了在App中支撑了千万级DAU,也慢慢将ReactNative跨端方案运用到了游戏,来提升开发、迭代效率。本次文章我们会分5个章节介绍我们在游戏中的一些探索和实践,相信大家也能从中有所收获:

(本篇为系列第四篇)

上面章节我们介绍了针对ReactNative做的大量的性能优化,ReactNative在手机端设备上优化很明显,但之前我们介绍了游戏相比于原生App,还多一个模拟器环境,与手机设备相差很大,运行在pc上,所以采用的的是x86架构,有32位与64位两类模拟器。ReactNative目前也已经支持了x86架构,所以在带有x86架构的游戏中,性能基本和手机设备旗鼓相当,但在海外一些游戏中,因只能支持v7、v8,不管是启动性能、内存、兼容性都相比x86版本很远,在某些复杂活动下启动性能能会超过3s以上,相信到这里,你们一定会问为什么海外不支持x86呢?主要原因是:Google Player对上架的App的架构有限制,要求v7和v8、x86和x86_64必须同时存在,换句话说游戏app如果要支持x86,就必须支持x86_64,而很多游戏引擎是不具备x86_64架构的,典型的如Unity,导致上架的App只有v7、v8,而x86模拟器也对v7、v8做了兼容,所以大部分的应用在模拟器上还是能用的,原理是采用了intel提供的转指令库,所以性能和兼容性会偏差,这里就不细述了。

另外还有其他一些限制,在设计整个框架时,考虑Androidx的问题(很多app没有升级),我们采用了0.59.9的ReactNative引擎,js引擎在Android端使用的是JavaScriptCore,由于JavaScriptCore最初是为桌面浏览器端设计,相较于桌面端,移动端能力有太多的限制。Facebook团队发现JavaScript 引擎是影响启动性能和应用包体积的重要因素,为了能从底层对移动端进行性能优化,2019 年推出一个强大的JS运行引擎,Facebook团队自建的JavaScrip引擎Hermes,目前ReactNative 0.60.2版本上已经支持了Android端,iOS在0.64版本也支持,新的引擎有几个特点:

  1. Hermes支持执行纯文本的js
  2. 支持动态加载纯文本js、bytecode
  3. 支持bytecode和纯文本js混合使用

同样我们也集成Hermes引擎到框架中,并做了大量的性能数据测试,大致的数据如下:

无论是在内存峰值、稳定值、启动性能上,Hermes相比于JSCore有了很多优化,Hermes业务包因为采用字节码,相对js混淆包增大了,但去掉source map后,整体大小是下降的。因ReactNative支持了JSI,所Hermes、JSCore在目前的ReactNative版本是共存的,大家可以使用0.60.2后的ReactNative版本体验:

  1. 配置android/app/build.gradle ,enableHermes: true 打包hermes引擎版本
  2. 配置chrome debug环境reactnative.dev/docs/hermes
  3. 其他开发方式不变

相信有不少App采用的Module方式集成的,升级Hermes也比较简单,只需将Hermes的aar库集成到工程中, Facebook已经将Hermes引擎托管到npm端,以下是参考代码:

   if (enableHermes) {

       def hermesPath = "../../node_modules/hermes-engine/android/";

       debugImplementation files(hermesPath + "hermes-debug.aar")

       releaseImplementation files(hermesPath + "hermes-release.aar")

   } else {

       implementation "org.webkit:android-jsc:+"

   }

另外要注意在ReactNative SDK中,如果JSCore和Hermes同时存在的情况下,会优先选择JSCore,当然这边逻辑是可以调整的,开发者可以将JSCore做了Hermes的backup方案,线上出现Hermes稳定稳定问题,可以降级到JSCore版本,毕竟JSCore版本已经稳定了很多版本了。

主要优化点

那facebook设计的Hermes引擎相对于JScore做了哪些优化?可以理解Hermes就是为ReactNative 而生的用于替换JScore的,ES6规范,采用AOT编译,将解释和编译过程前置到编译阶段,运行时只完成机器码的执行,大大提高了运行效率:

  1. 相比JSCore,做了简化,将一些ReactNative中不常用的语法做了删除,更加聚焦、精简,具体可以参考Features,以下是一些例子:
  • Realms
  • with statements
  • Local mode eval() (use and introduce local variables)
  • Symbol.species and its interactions with JS library functions
  • use of constructor property when creating new Arrays in Array.prototype methods
  • Symbol.unscopables (Hermes does not support with)
  • Other features added to ECMAScript after ES6 not listed under "Supported"
  1. 最新版本Hermes 采用了Hades垃圾收集器,目的是要对比genGC的暂停时间提升一个数量级,大部分垃圾收集工作是在后台线程中运行,与运行JavaScript代码的解释器是同时发生的,而genGC是与解释器共享的单个线程运行
  2. 无JIT, 为了加快执行速度,流行的 JavaScript 引擎可以地将频繁解释的代码编译为机器码,这项工作由即时(JIT)编译器执行, 但JIT 必须在应用程序启动时预热,所以对TTI影响较大,另外JIT 会增加原生代码体积和内存消耗,这些在内存比较吃紧的手持设备、游戏中,显得很致命,影响了我们最关注的指标,因此Hermes没有实现JIT,而是更关注Hermes 的解释器执行效率
  3. Hermes预编译器,在移动应用构建过程中运行,或者是打包前端业务包时,原有的方式,会将前端代码打包成混淆的jsbunlde文件,而Hermes提供了转换器,将jsbundle文件转换成字节码文件,因为是在打包过程中,所以给到我们程序优化成字节码的时间可以更长,使字节码更小、效率更高,而且现在还可以针对整个程序做优化,例如删除重复数据和打包字符串表等。另外字节码的设计使其在运行时可以映射到内存中并解释,而无需一次读取整个文件,以下是具体的执行命令:
./node_modules/hermes-engine/osx-bin/hermes -emit-binary
  1. 上面介绍了JSCore和Hermes在ReactNative中是可以随时切换的,新的引擎支持了JSI架构,抹平了JS引擎的差异,切换到其他引擎更方便了,例如v8等

  1. Hermes是为移动设备设计的JS引擎,稳定性更好,在x86架构上,TTI表现相比JScore提升较少,但x86的整体性能已经很好了
  2. 对于文章前面提到的JSCore v7、v8在x86模拟器中中存在的兼容问题,Hermes引擎也有了很好的改善,基本兼容主流模拟器

数据来看,新的引擎无论是在TTI上,还是执行效率、兼容性上,都相比旧的JSCore有了飞的提升,到这里大家会有一个疑问,如果是打包成字节码运行,那怎么调试的,其实Hermes在调试过程中,采用的是懒编译,它在设备上懒惰地生成字节码。这样开发者就可以使用 Metro 或其他纯 JavaScript 代码源进行快速迭代,但代价是懒惰编译的字节码不包括生产构建的所有优化特性,性能上会偏差,这也是为什么非字节码的jsbundle能运行到Hermes的原因,但整体性能偏差。

如何升级

上面说了很多Hermes的新特性,相信大家也一定也想在自己的项目中体验体验,那就涉及升级的问题了,可以有三种思路:

  • 升级ReactNative版本到0.60.2及以后版本,当然考虑稳定性,版本越高越好,具体升级版本,大家根据实际需求来升级,Facebook也提供了升级工具:react-native-community.github.io/upgrade-hel…
  • 如项目使用的ReactNative已经包含了JSI的设计,也可以考虑通过适配JSI和Hermes的excute环境来完成适配,因JSI可以支持灵活替换JS引擎,但相对适配的难度相较大
  • 对于一些已经集成Hermes的项目,单独升级Hermes版本的难度较低,兼容适配JSI接口即可。笔者也尝试在0.62.2的ReactNative版本中完成了Hermes从0.40升级到0.72

Hermes版本的修改记录github.com/facebook/he…

总结

我们在游戏中完成Hermes升级后,将ReactNative版本到Hermes版本,通过真实的线上业务来验证、灰度,整体数据基本超过了预期的,对用户体验来说收益很高:

  1. 整体崩溃率是远低于JSCore版本
  2. 模拟器TTI时间有几倍以上的优化