3

Since its first release in 2019, the small and lightweight JavaScript engine Hermes has become more and more famous in the community, and many frameworks have begun to support Hermes. As the creator of the popular meta-framework in the React Native field, the Expo team previously announced experimental support for Hermes. In addition, the popular mobile database Realm team recently decided to provide alpha support for Hermes.

In this article, we want to highlight the exciting developments in the past two years that have made Hermes the best JavaScript engine for React Native. Looking ahead, we are confident that through more improvements, Hermes will become the default JavaScript engine in React Native on various platforms.

Optimized for React Native

The function definition in Hermes is responsible for indicating how to perform compilation work in advance. In other words, Hermes-enabled React Native applications will come with pre-compiled and optimized bytecode instead of pure JavaScript source code. This greatly reduces the workload required for users to start the product. Quantitative tests from Facebook and other applications in the community show that enabling Hermes can generally reduce the TTI (ie, interaction time) indicators of a product by nearly half.

在这里插入图片描述
But we will not stop there. We are always committed to improving Hermes in all aspects and working hard to make it the best JavaScript engine dedicated to React Native.

Create a new garbage collector for Fabric

Fabric renderer, which has emerged in the new generation of React Native architecture, is highly anticipated. It can call JavaScript synchronously on the UI thread. However, if the execution time of the JavaScript thread is too long, it will cause obvious UI frame drop and make the user unable to input normally.

The concurrent rendering mechanism provided by React Fiber can split the rendering work into multiple blocks, thereby avoiding a single JavaScript task from taking too long. In addition, there is another common source of latency in JavaScript threads-the garbage collection (GC) mechanism. Because once garbage collection starts, the entire JavaScript engine must put aside all the work at hand to perform garbage collection.

The original default garbage collector GenGC belongs to a single-threaded generational garbage collection scheme. Among them, a typical half-space copy strategy is adopted for the new generation, and a mark-compact strategy is used for the old generation, so as to more actively return the memory to the operating system.

On complex applications like Facebook for Android, we observed an average pause duration of 200 milliseconds, while the 99th percentile pause was 1.4 seconds. Considering the large and diverse user base of Facebook for Android, the most extreme pause time can even be as long as 7 seconds.

In order to alleviate this situation, we have established a new, concurrency-oriented garbage collection program, namely Hades . Hades also adopts a generational design, and its new generation collection method is exactly the same as GenGC, while the old generation collection method is managed by a snapshot tag scanning collector.

Hades is able to hand over most of the workload to background threads for execution, thereby significantly shortening the duration of garbage collection pauses without preventing the main thread of the engine from continuing to execute JavaScript code. Our statistics show that Hades has a latency of 48 milliseconds at the 99.9th percentile on a 64-bit device (34 times faster than GenGC!), while the latency at the 99.9th percentile on a 32-bit device is about 88 milliseconds ( Run as a single-threaded incremental GC).

However, due to the need for a higher resource cost write barrier, a slower free list-based allocation mechanism (as opposed to a collision pointer allocator), and more heap fragments, Hades is actually trading overall throughput for shorter pauses time. We believe that this choice is in line with user habits, and will also achieve a lower overall memory footprint by merging with other memory optimization mechanisms that will be discussed next.

Improve performance issues

The startup time of an application is very important for many application products, and we also hope to continue to increase the performance limit of React Native. For all JavaScript functions implemented in Hermes, we will carefully monitor their impact on production performance and ensure that they will not lower performance indicators.

At Facebook, we are currently experimenting with a dedicated Babel transform profile for Hermes in Metro, hoping to replace the original more than ten Babel transforms with the native ESNext implementation in Hermes. In this way, we have improved the TTI by 18% to 25% in direct observation, and the overall bytecode has been significantly thinned; hope that similar results can be obtained in OSS in the future.

In addition to startup performance, we also see memory footprint as an important opportunity to improve React Native applications, which is also a prerequisite for a good virtual reality experience. Therefore, in the underlying control of the JavaScript engine, we use compression bits and bytes to achieve multiple rounds of memory optimization:

  1. Previously, all JavaScript values were expressed as a 64-bit NaN box-encoded marked value form to represent 64
    Double-precision floating-point values and pointers on the bit architecture. But this method is a huge waste in practice, because most numbers are actually small integers (SMI), and the JavaScript heap of the client application is usually no greater than 4 GiB. To solve this problem, we introduced a new 32-bit encoding, in which SMI and pointers are encoded in 29 bits (because the pointers are 8-byte aligned, it can be assumed that the bottom 3 bits are always zero), and the rest of the JS numbers are boxed On the pile. As a result, the heap size of JavaScript is reduced by about 30%.
  2. Different types of JavaScript objects are represented as different GC management units on the JS heap. By actively optimizing the memory layout of these unit heads, we were able to further reduce the memory footprint by about 15%.

In this regard, we also made a key decision in Hermes, that is, no longer use just-in-time (JIT) compiler. Because we believe that for most React Native applications, the extra warm-up cost, extra binary files and even memory footprint are meaningless.

Over the years, we have invested a lot of effort in interpreter performance and compiler optimization, which has also allowed Hermes to obtain a React Native workload throughput advantage that far exceeds other engines. We will continue to pay attention to the widespread performance bottlenecks (interpreter scheduling loop, stack layout, object model, garbage collection, etc.) to improve throughput.

Vertical integration field

At Facebook, we are accustomed to using large monorepo hosting projects. By closely iterating the engine (Hermes) and host (React Native) together, now we have opened up a broad space for vertical integration. Here are a few specific examples:

  • Hermes uses the Chrome DevTools protocol to support the use of the Chrome debugger to perform JavaScript debugging on the device. This method is better than the traditional "remote JS debugging" (using in-app proxy to run JS on desktop Chrome) because it supports debugging and synchronous native calls and guarantees a unified runtime environment. Together with React DevTools, Metro, and Inspector, the Hermes debugger has now become an integral part of Flipper, providing a good one-stop developer experience together.
  • Objects allocated in the initialization path of React Native applications often exist for a long time and do not meet the generational assumptions put forward by generational GC. Therefore, when we configure Hermes in React Native, we will allocate the first 32 MiB directly to the old generation (ie pre-tenuring) to avoid triggering GC pauses and delaying TTI.
  • The new React Native architecture is largely based on JSI (that is, JavaScript Interface), which is a lightweight general-purpose API for embedding JavaScript engines into C++ programs. By letting the team that maintains the JS engine maintain the JSI API implementation at the same time, we are confident to provide the best integration effect. And this integrated solution has been tested in actual combat on Facebook's large-scale business, and has good reliability and operational efficiency.
  • Having JavaScript concurrency primitives (such as promises) and platform concurrency primitives (such as microtasks) with correct semantics and good performance is essential for React concurrent rendering and the future development of React Native applications. Historically, the promise in React Native used the non-standard setImmediate API as a putty script. We are working hard to implement native promises and microtasks from the JS engine through JSI, and introduce queueMicrotask (a new item in the web standard) to the platform to better support modern asynchronous JavaScript code.

Community development

Facebook attaches great importance to the Hermes project, but only when a complete ecosystem, especially the technical community, is established for Hermes, can the development work really come to an end. And only in this way, everyone can make full use of the functions of Hermes and realize its potential.

Expand to more new platforms

Hermes was originally only open sourced for React Native on Android. After this, we are very pleased to see community members gradually expand the scope of Hermes support, and has now expanded to React Native ecosystem covered by a variety of other platforms.

Callstack is the first to Hermes to iOS in React Native 0.64. They also published a series of feature articles and launched a podcast to show users how they achieved this goal. According to the benchmark test, compared with the JSC of the Mattermost application, Hermes's startup performance on iOS can be steadily improved by about 40%, the memory footprint is reduced by about 18%, and the memory usage during the application is only 2.4 MiB.

Microsoft continues to Hermes to React Native on At the Microsoft Build 2020 conference, the software giant said that compared to the original Chakra engine, Hermes can reduce the memory footprint of React Native for Windows by 13%. In some recent comprehensive benchmark tests, Microsoft found that Hermes 0.8 (including Hades and the previously mentioned SMI and pointer compression optimization functions) occupies 30% to 40% less memory than other engines. There is no doubt that the desktop version Messenger video call experience based on React Native has also been significantly improved with the support of Hermes.

More importantly, Hermes has also been providing support for various virtual reality experiences built on Oculus using the React series of technologies, including Oculus Home.

Community support

We admit that there are still some problems in Hermes that hinder the smooth intervention of more communities, and we are also working hard to build support for these missing functions. Our goal is to achieve full functionality as soon as possible, making Hermes the best choice for most React Native applications. The following is the Hermes development roadmap being planned by the community:

  • Since Facebook is not used, Proxy and Reflect were initially excluded from Hermes. We were worried that even if we didn't really use it, adding Proxy rashly would harm the performance of attribute lookup. But with the libraries as 1617ea87d2eb7d MobX 1617ea87d2eb7e and Immer , Proxy quickly became the most popular feature among Hermes. After careful evaluation, we decided to provide a dedicated proxy for the community and managed to achieve it at a very low cost. Since Facebook does not use this feature, it can only rely on the technical community to prove its stability. We first started the proxy test in the form of marking and creating opt-in npm packages in the 0.4 and 0.5 versions, and enabled it by default 0.7 version
  • ECMAScript Internationalization API Specification (referred to as ECMA-402 or Intl) is also the focus of users’ voices. Intl represents a huge set of APIs, which usually require 6 MB of Unicode CLDR data to be implemented. Because of this, FormatJS (aka react-intl) and JS engines such as the international variant of JSC in the are so bloated and clumsy. In order to avoid unnecessary expansion of the Hermes binary file volume, we decided to directly use and map the ICU facilities provided by the operating system's built-in library. The corresponding price is to introduce some (usually minor) differences in certain cross-platform behaviors.
  • Microsoft cooperated to complete the build support work on Android. It covers almost everything from ECMA-402 to ES2020, while the impact on volume is only 3% (each ABI is only 57 K to 62 K). Our Twitter found that users strongly support the inclusion of Intl by default, so we decided to introduce this feature from version 0.8.
  • Facebook has sponsored Major League Hacking to initiate a remote open source scholarship program. Last year, we launched the Hermes sampling analyzer; this year, our researchers will work with members of Hermes, React Native, and Callstack to implement support for Hermes Intl on iOS.
  • Thank you for your feedback that the default heap size limit is too low, causing many users who are not familiar with the custom Hermes GC configuration to produce unnecessary GC pressure and even OOM crash . Therefore, by default, we will increase the upper limit from 512 MiB to 3 GiB. This configuration should be more than enough for most users.
  • It has been reported that our dedicated Function.prototype.toString implementation will cause the library to perform incorrect feature detection and cause performance degradation, and make it impossible for users to perform source code injection. While solving the problem, we have also strengthened Hermes' determination to respect the facts and try to avoid hindering the smooth use of developers.

Summarize

In short, our vision is to make Hermes the default JavaScript engine on all React Native platforms. We are working hard in this direction and hope to fully listen to everyone's feedback from different perspectives.

Only when we are fully prepared can we lay a solid foundation for the broad acceptance of Hermes in the ecosystem. Here, we sincerely invite everyone to experience Hermes and submit all the suggestions, comments, feature requests and incompatibility errors you find to our GitHub repo.

Original link: Toward Hermes being the Default


xiangzhihong
5.9k 声望15.3k 粉丝

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