Author: Xiong Wenyuan, Byte Game Client team

Client cross-end frameworks have been developed for many years. Recently popular aprons, Flutter and ReactNative are all successful and mature frameworks, which are targeted at different developers and widely used by many large apps. I was fortunate to participate in learning and using these excellent cross-end solutions early. In recent years of development and architecture design, in addition to supporting tens of millions of DAU in the App, ReactNative cross-terminal scheme was gradually applied to the game to improve the efficiency of development and iteration. In this article, we are going to introduce some of our explorations and practices in games in five chapters, which you can learn from:

  • Part 1: Background on using ReactNative in games
  • How to integrate ReactNative into a game
  • ReactNative performance optimization in games
  • ReactNative Hermes Engine Introduction
  • Chapter 5: Introduction to ReactNative’s new architecture

(This is the fourth part of the series)

In the previous chapter, we introduced a lot of performance optimization for ReactNative. The optimization of ReactNative on mobile devices is obvious. However, we introduced that compared with native App, the game also has a simulator environment, which is quite different from mobile devices and runs on PC, so it adopts x86 architecture. There are two types of simulators: 32-bit and 64-bit. ReactNative currently supports x86 architecture, so in games with x86 architecture, the performance is basically the same as that of mobile devices. However, in some overseas games, since they can only support V7 and V8, the boot performance, memory and compatibility are far behind that of x86 versions. In some complex activities, the startup performance can exceed 3s. At this point, you must ask why x86 is not supported overseas. The main reasons are: In other words, if a game App is to support x86, it must support X86_64. Many game engines do not have x86_64 architecture, such as Unity. As a result, only V7 and V8 apps are available on the shelves, while x86 emulators are compatible with V7 and V8, so most applications can still be used on the emulators. The principle is to use the transfer instruction library provided by Intel, so the performance and compatibility will be different, I won’t go into details here.

In addition, there are some other limitations. When designing the whole framework, considering the problems of Android X (many apps have not been upgraded), we adopted the 0.59.9 ReactNative engine, and JavaScriptCore was used in the JS engine on the Android side. Since JavaScriptCore was originally designed for the desktop browser side, the mobile side has many limitations compared to the desktop side. Facebook team found that JavaScript engine is an important factor affecting startup performance and application package size. In order to optimize the performance of mobile terminal from the bottom, a powerful JS running engine was launched in 2019. Hermes, the JavaScript engine built by Facebook team, ReactNative 0.60.2 is available for Android and 0.64 for iOS. The new engine has several features:

  1. Hermes supports javascript that executes plain text
  2. Support dynamic loading of pure text JS, Bytecode
  3. Support bytecode and plain text JS mixed use

We also integrated the Hermes engine into the framework and did a lot of performance data tests. The general data are as follows:

Compared with JSCore, Hermes has a lot of optimization in terms of memory peak value, stable value and startup performance. Hermes business package is larger than JS obtrusion package due to bytecode, but the overall size decreases after removing source Map. Since ReactNative supports JSI, Hermes and JSCore coexist in the current ReactNative version, you can use the ReactNative version after 0.60.2 to experience:

  1. Configure Android /app/build.gradle, enableHermes: true to pack the Hermes engine version
  2. Configuration chrome reactnative debug environment. Dev/docs/hermes
  3. Other development methods remain unchanged

I believe that many apps are integrated in Module mode, and it is relatively easy to upgrade Hermes. You only need to integrate the AAR library of Hermes into the project. Facebook has hosted the Hermes engine on the NPM side, and the reference code is as follows:

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:+" }Copy the code

In addition, it should be noted that in ReactNative SDK, if JSCore and Hermes exist at the same time, JSCore will be preferred. Of course, the logic here can be adjusted. Developers can make JSCore into the backup scheme of Hermes, and Hermes stability problems occur online. You can downgrade to the JSCore version, after all, the JSCore version has been stabilized for many versions.

Main optimization points

What improvements did Facebook make to the Hermes engine compared to JScore? It can be understood that Hermes is the ES6 specification for ReactNative to replace JScore. It adopts AOT compilation, presets the interpretation and compilation process to the compilation stage, and only performs machine code execution at runtime, greatly improving the operation efficiency:

  1. Compared with JSCore, it has been simplified and deleted some grammar that is not commonly used in ReactNative to make it more focused and concise. For details, please refer to Features. Here are some examples:
  • 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. The latest version of Hermes uses the Hades garbage collector, which is intended to increase pause times by an order of magnitude compared to genGC, where most of the garbage collection is run in background threads at the same time as the interpreter running JavaScript code, while genGC is run in a single thread shared with the interpreter
  2. Without JIT, in order to speed up execution, popular JavaScript engines can compile frequently interpreted code into machine code. This work is performed by just-in-time (JIT) compilers, but the JIT must be warmed up at application startup, so TTI has a big impact. Additionally, JIT increases native code size and memory consumption. These can be deadly in memory-constrained handheld devices, games, and affect the metrics we care about most, so Instead of JIT implementation Hermes focuses more on Hermes interpreter execution efficiency
  3. The Hermes precompiler, when running during mobile application building, or when packaging front-end business packages, traditionally packs front-end code into confused Jsbunlde files, whereas Hermes provides converters to convert jsbundle files into bytecode files, because during packaging, So it takes longer for our program to optimize to bytecode, making bytecode smaller and more efficient, and now it’s possible to make optimizations for the entire program, such as removing duplicate data and packing string tables. In addition, bytecode is designed so that it can be mapped to memory and interpreted at run time without having to read the entire file at once. Here are the specific execution commands:
./node_modules/hermes-engine/osx-bin/hermes -emit-binary
Copy the code
  1. As mentioned above, JSCore and Hermes can be switched at any time in ReactNative. The new engine supports JSI architecture and smoothen the differences between JS engines, making switching to other engines more convenient, such as V8, etc

  1. Hermes is a JS engine designed for mobile devices. It is more stable. On x86, TTI performance is less improved than JScore, but the overall performance of x86 is very good
  2. The Hermes engine is also a significant improvement on the JSCore V7 and V8 compatibility issues mentioned earlier in the article in x86 emulators, and is basically compatible with mainstream emulators

Data, a new engine in TTI, or execution efficiency, compatibility, are compared with the old JSCore had to fly to ascend, here you will have a question, if the bytecode is packaged into running, that how to debug, in fact, in the process of debugging, USES is lazy to compile, it idly on the equipment to generate bytecode. This allows developers to iterate quickly using Metro or other pure JavaScript code sources, but at the cost of lazy-compiled bytecodes that don’t include all the optimized features of production builds, resulting in performance bias, which is why non-bytecode Jsbundles can run to Hermes, but overall performance bias.

How to upgrade

There are a lot of new Hermes features mentioned above, and I’m sure you will also want to experience them in your own projects. There are three ways to think about upgrading.

  • Update ReactNative to 0.60.2 or later. Of course, considering the stability, the higher the version, the better. You can upgrade the specific version according to your actual needs. The react – native – community. Dead simple. IO/upgrade – hel…
  • If the ReactNative used in the project already includes JSI design, you can also consider adapting JSI to the Excute environment of Hermes. JSI supports flexible replacement of JS engines, but it is relatively difficult to adapt
  • For some projects that already integrate Hermes, it is less difficult to upgrade the Hermes version separately, and it can be compatible with the JSI interface. I also tried to upgrade Hermes from 0.40 to 0.72 in the 0.62.2 ReactNative version

Changes to the Hermes version: github.com/facebook/he…

conclusion

After completing the Hermes upgrade in the game, we transferred the ReactNative version to the Hermes version, verified and grayscale through real online business, and the overall data basically exceeded expectations, which is very profitable for user experience:

  1. The overall crash rate is much lower than the JSCore version
  2. Emulator TTI time has several times more optimization