What is Hermes?
In 2019, Meta announced Hermes and open-sourced it to the React Native community. In 2022, Hermes was shipped as the default engine alongside React Native updates, replacing JSC.
Hermes has been key in making React Native a more competitive native framework that can contend with Swift or Kotlin. Here, we’ll discuss the Hermes predecessor, Hermes’s design decisions, and how Hermes could impact React Native long term.
There are two ways to understand the issue with JSC: an empirical case study and a high-level paradigm exploration.
An empirical historical example
Meta created React Native to consolidate development patterns with its main React web application. Meta uses React Native for most of its mobile applications, including its flagship app, which includes Facebook Marketplace.
Marketplace is built entirely with React Native. But because Marketplace displays countless products to users—and by extension, has easily hundreds of subviews—it historically took a long time to load. This phenomenon is known as having a poor TTI, or time-to-interact.
But loading wasn’t the only issue. Marketplace struggled with garbage collection (the process of freeing memory no longer used by the application). As users scrolled Marketplace quickly, a massive garbage collection job would eventually cause the app to freeze. This “stop the world” (STW) effect and the often laggy experience tanked user sentiment around Marketplace.
To be fair, some users never noticed a thing. If they were running Facebook Marketplace on the newest phone models, their powerful hardware often hid the inefficiencies by working really hard to compensate for the app’s deficiencies. But Facebook is a massive product used across the globe, and for devices with more limited hardware, these issues were make-or-break matters against a positive user experience.
Something had to change.
Eliminating the inherent awkwardness of React Native
Understanding the new Hermes flow
Hermes doesn’t dramatically change the pipeline used by JSC —it just rearranges it. To best understand this shift, let’s explore the steps from compilation to execution.
The old JSC pipeline
Before Hermes, a JSC pipeline was evenly split between build time and run time.
The new Hermes pipeline
Hermes moves almost all processes to build time instead.
Now, parsing functions, compiling bytecode, and optimizing bytecode happen on a developer’s computer once; since developers’ computers these days are pretty beefy, this is a better allocation of resources. Afterward, end user devices just load and execute that bytecode at runtime.
How Hermes accomplishes AOT compilation
Hermes achieves ahead-of-time (AOT) compilation through a few intermediate steps.
Then, Hermes compiles SSA IR into optimized bytecode designed for the device. Because bytecode is optimized and succinct, Hermes can often ship a much smaller bundle size on Android devices. (iOS bundles are already fairly minified, so Hermes actually marginally increases the bundle size.)
The Hermes garbage collector, Hades
In earlier versions, Hermes used JSC’s original garbage collector, GenGC (Generational Garbage Collector). GenGC split memory segments into two generations, a Young Generation (YG) and an Old Generation (OG). The YG would go through an efficient process, where memory was pruned if it showed obvious signs that the segment was freed. The remaining allocated memory in YG was then allocated to the OG. The OG would run through Cheney’s algorithm, a CPU-consuming process that split the heap, ping-ponged memory between halves, and trimmed it down in stages. Because GenGC is single-threaded, running on the same thread as the application’s main JS interpreter, its OG process would freeze up complex apps. creating an STW side effect.
Hermes’s own garbage collector, Hades, extends GenGC’s YG and OG, but radically re-engineers how the OG works. Now OG undergoes a sequence of stages—Mark, Sweep, and Compact—which more accurately frees up memory by leveraging various data structures and locks. Even more crucially, Hades’s OG runs concurrently with the main interpreter thread. As a result, OG doesn’t freeze the main application—and given that OG was always more CPU-intensive than YG, this makes a monumental difference over GenGC.
There’s a downside to this approach: because Hades runs OG in a background thread and implements a more involved algorithm, memory is freed more slowly. On the other hand, because Hades is more accurate than GenGC, more memory is ultimately freed. The Hermes team contends that the performance improvements of Hades overwhelm the entry-to-exit speed of segments undergoing garbage collection. And given Hermes’s overall goal of targeting better application performance, this prioritization of minimizing freezes and decreasing net memory consumption is aligned with its design philosophy.
How successful is Hermes?
Hermes has put up some impressive benchmarks that demonstrate its success. In particular, Meta’s benchmarks for Hermes averaged as follows:
- Time To Interact (TTI): 51% decrease on Android and 63% decrease on iOS
- Build Size: 19% decrease on Android but a 19% increase on iOS (noting that iOS build sizes were typically quite smaller than Android build sizes, so the tradeoff is a net positive.)
- Memory Utilization: 23% decrease on Android and 11% decrease on iOS
Additionally, while it can be difficult to measure STW memory effects scientifically since they happen sporadically, Hermes almost eradicates the issue with Hades moving the OG process to a background thread.
Hermes’ compatibility with the existing developer ecosystem
Hermes has been widely embraced by developers and the greater React Native developer ecosystem.
For example, Expo, a popular React Native framework with a robust testing suite, has also shifted to Hermes being its default engine. Expo apps have shown similar improvements to app metrics since the switch.
And Hermes used to be exploitable to run Doom—yes, the legendary 1990s video game— which is basically a rite-of-passage to become a respected developer product!
Does Hermes have any limitations?
While Hermes has largely had positive impacts, it lacks certain features defined by its target specification, ECMAScript 2015. However, most missing features either don’t make sense for React Native, are minor, or can be handled by a transpiler like Babel.
Previously, some of these limitations restricted developers from using Hermes altogether. Asynchronous features like
await originally weren’t supported, but they have since been added. (The official documentation is still a bit outdated at the time of writing.)
WeakRef, which were initially unavailable, were made available in 0.70, providing support to a lot of applications that deal with large numbers or are memory-conscious. Others, like
const remain unsupported but, practically, are supposed by Hermes’ dedicated Babel transform profile.
Two of the most popular unsupported features were
Reflect. Previously, Meta expressed reluctance to support these, concerned that they could slow Hermes down overall. But popular state libraries like MobX and Immer depend on
Reflect—andReact Native 0.70 shipped with a Hermes engine that supported both by default.
with statements or the constructor property. Others don’t make as much sense for React Native to begin with, such as Realms, which are what enable multiple <script> tags to work together seamlessly in browsers.
Overall, after a few updates post-launch, Hermes now supports a majority of ECMAScript 2015 features that are relevant to React Native developers.