Facebook recently unveiled its next generation JavaScript execution engine Hermes at ChainReact 2019. Hermes is a small, lightweight JavaScript engine optimized for running React Native on Android. For many applications, simply enabling Hermes reduces startup time, memory usage, and application size, and because it is implemented as a JavaScript standard, it is easy to integrate with React Native applications.

Introduction of Hermes

Since the launch of ReactNative, a large number of apps have been accessed and used, including the main process business of large applications. As business complexity increases, performance issues become impossible to ignore.

When analyzing the performance data, the Facebook team found that the JavaScript engine was a significant factor in startup performance and application package size. Since JavaScriptCore was originally designed for the desktop browser, compared with the desktop, the mobile terminal has too many limitations. In order to optimize the performance of the mobile terminal from the bottom, the Facebook team chose to build their own JavaScrip engine and designed Hermes, which was limited by the iOS AppStore review. Currently only available on Android.

The official Hermes engine test data was presented at The Chain React conference:

  • The Time from page startup To user actionable (TTI) decreased from 4.3 seconds To 2.01 seconds
  • App download size reduced from 41MB to 22MB
  • Memory footprint, reduced from 185MB to 136MB

It can be seen that after switching to Hermes, the loading time, App size and memory usage all improved significantly.

Hermes Optimization

In mobile application development, memory size and application size are important indicators to measure the quality of an application. Therefore, Hermes also optimizes React Native applications from these aspects.

Bytecode precompilation

Typically, JavaScript engines parse JavaScript source code and generate bytecodes after loading, and JavaScript code needs to start executing after bytecodes are generated. To skip this step, Hermes introduces a precompiler that runs during the mobile application build process. This can take longer to optimize bytecode, making it smaller and more efficient. It is now possible to optimize the entire program, such as removing duplicate data and packaging string tables.

Bytecode is designed so that it can be mapped to memory and interpreted at run time without having to eagerly read the entire file. Poor-performing flash I/O on many mid – and low-end mobile devices significantly increases latency, so volumeoptimized bytecode loading from flash on demand can significantly improve TTI. In addition, because memory is mapped read-only and supported by files, mobile operating systems that do not use virtual memory, such as Android, can clear these pages when they run low on memory, thereby reducing process killing on devices with less memory.

Without a JIT

To speed up execution, popular JavaScript engines can compile frequently interpreted code into machine code, which is performed by just-in-time (JIT) compilers.

Hermes does not currently have a JIT compiler. This means Hermes will not perform well in some benchmarks, particularly those that rely on CPU performance. This design is deliberate: these benchmarks hardly reflect the actual workloads of mobile applications. We have done some experiments with JIT, but we believe that to achieve real speed improvements, we need to focus on the above realistic metrics. Because JIts must warm up at application startup, they are difficult to improve TTI and may even damage TTI. In addition, JIT increases native code volume and memory consumption, which negatively impacts our key metrics. JIT can be a drag on the metrics we care about most, so we choose not to implement it.

Garbage Collection strategy

Efficient use of memory in mobile devices is very important. In general, low-end devices tend to have limited memory, so the operating system forces applications that use too much memory to be killed. When an application is killed and used again, it needs to be slowly restarted, and background functions are affected. In early testing we learned that virtual address (VA) space, especially contiguous VA space, can be a limited resource when running large applications on 32-bit devices, and lazy allocation with physical pages is not helpful.

Therefore, in order to maximize the memory and VA space used by the engine, we built a garbage collector with the following functions, the main measures are:

  • Allocation on Demand: VA space is allocated in blocks only when needed.
  • Discontinuous: THE VA space does not have to be in a single memory range, which avoids resource limitations on 32-bit devices.
  • Move: Being able to move objects means you can defragment memory and return blocks that are no longer needed to the operating system.
  • Generational: Not scanning the entire JavaScript heap each time GC, reducing GC time.

Integration of Hermes

Get to know Hermes quickly

The Faceback team has uploaded the Hermes tool to NPM: HermesVM. The Hemres tool can run JS code directly, convert bytecode, and provide a wide range of parameters for tuning control.

For example, hermesVM executes JS code and transforms bytecode functions as follows:

// create a hermes_test file.print("This is Hermes Demo"); Hermes_test.js This is hermes Demo // convert to bytecode ~/node_modules/hermesvm/osx-bin/hermes --emit-binary hermes_test.js -out hermes_test.hbc // Run the bytecode ~/node_modules/hermesvm/osx-bin/hermes hermes_test.hbc This is Hermes DemoCopy the code

Integration in new projects

Currently Hermes is an optional React Native feature. To enable Hermes, make sure the React Native project is 0.60.2 or older and make the following changes to the Android /app/build.gradle file.

project.ext.react = [
  entryFile: "index.js".enableHermes: true
]
Copy the code

If the application has been built at least once, run the following command to clear it.

cd android && ./gradlew clean
Copy the code

The application can then be developed and deployed normally.

react-native run-android
Copy the code

debugging

To provide a great debugging experience, we implemented remote debugging support for Chrome through the DevTools protocol. As of today, React Native only supports in-app proxy debugging when running your app’s JavaScript code in Chrome. With this support you can debug your application, but the React Native bridge doesn’t sync Native calls. Hermes’s support for remote debug protocols allows developers to connect to the Hermes engine running on their device and debug their applications natively using the same engine as in production. In addition to debugging, we are also considering implementing additional support for the Chrome DevTools protocol.

Comparison of Hermes, JavaScriptCore, and V8

After official data validation, the Key metrics proposed by the Faceback team were significantly improved compared to the original JavaScriptCore solution.

First, in terms of the size of native SO files, Hermes is about 16% smaller than JavaScriptCore in the necessary SO libraries that RN relies on (armeabi architecture alone is about 0.5m smaller after compression), while V8 is much larger than Hermes and JavaScriptCore.

RNTester

Problems with Hermes

Hermes does have many advantages over JavaScriptCore, but not necessarily better than JavaScriptCore. As testing and integration progressed, problems with Hermes became more and more obvious.

The bytecode file occupies too large a size

In tests, Hermes compiled bytecode files that were 100% larger than plain text JS files. Therefore, outgoing RN packets will be large, and dynamic delivery of RN incremental packets will reduce the differential efficiency because it is a binary file diff.

In order to solve this problem, according to the characteristics of Hermes, we changed our thinking and put bytecode compilation of Hermes into the client. The client stores JS and Bytecode files at the same time. If bytecode compilation is completed, Hermes will be used. Otherwise, JavaScriptCore is still used.

The Hermes open source project provides a complieJS method for compiling Bytecode, but this part of the code is not packaged by default into RN’s Hermes engine. We slightly integrate, package, and expose it through JNI for business use.

It takes a long time to execute plain text JS

We let Hermes load the plain text before the client converts the plain text JS to bytecode. In actual tests, however, Hermes was found to load plain text nearly 30% slower than JavaScriptCore. The main reason is that Hermes has removed the JIT function, which makes it slow to run plain text JS code.

Continue to optimize

To simplify migration of Hermes and continue to support JavaScriptCore on iOS, we built JSI; This is a lightweight API for embedding JavaScript engines in C++ applications. This API enables React Native engineers to implement their own infrastructure improvements. Fabric uses JSI, which preempts React Native rendering; TurboModules also uses JSI, which reduces the size of Native modules and can be loaded lazily according to the needs of React Native applications.

Related links:

hermesengine.dev/

www.oschina.net/p/hermes-js…

www.infoq.cn/article/8JE…