The previous default garbage collector in Hermes, GenGC, was a single-threaded generational garbage collector. The new generations uses a typical semi-space copying strategy, and the old generations uses a mark-compact strategy to make it really good at aggressively returning memory to the operating system. Due to its single-thread, GenGC has the downside of causing long GC pauses. On apps that are as complicated as Facebook for Android, we observed an average pause of 200ms, or 1.4s at p99. We have even seen it be as long as 7 seconds, considering the large and diverse user base of Facebook for Android.
One of our key decisions with Hermes was to not implement a just-in-time (JIT) compiler because we believe that for most React Native apps, the additional warm-up costs and extra footprints on binary and memory would not actually be worthwhile. For years, we invested a lot of effort in optimizing interpreter performance and compiler optimizations to make Hermes’s throughput competitive with other engines for React Native workloads. We are continuing to focus on improving throughput by identifying performance bottlenecks from everywhere (interpreter dispatch loop, stack layout, object model, GC, etc.). Expect some more numbers in upcoming releases!
At Facebook, we prefer to colocate projects within a large monorepo. By having the engine (Hermes) and the host (React Native) closely iterating together, we opened a lot of room for vertical integrations. To name a few:
setImmediateAPIs. We are working on making native promises and microtasks from JS engines available via JSI, and introducing
Hermes has been really great for us at Facebook. But our work is not done until our community can use Hermes to power experiences throughout the ecosystem, so that everyone leverage all of its features and to embrace its full potential.
Hermes was initially open sourced only for React Native on Android. Since then, we have been thrilled to see our members of the community expanding Hermes support into many other platforms that React Native’s ecosystem has expanded.
Callstack led the effort of bringing Hermes to iOS in React Native 0.64. They wrote a series of articles and hosted a podcast on how they achieved it. According to their benchmarks, Hermes was able to consistently deliver ~40% improvement to startup and ~18% reduced memory on iOS compared to JSC for the Mattermost app, with only 2.4 MiB of app size overhead. I encourage you to see it live with your own eyes.
Microsoft has been bringing Hermes to React Native for Windows and macOS. At Microsoft Build 2020, Microsoft shared that Hermes’s memory impact (working set) is 13% lower than the Chakra engine on React Native for Windows. Recently, on some synthetic benchmarks, they’ve found Hermes 0.8 (shipped with Hades and aforementioned SMI and pointer compression optimization) uses 30%-40% less memory than other engines. Not surprisingly, the desktop Messenger video calling experience built on React Native, is also powered by Hermes.
Last but not least, Hermes has also been powering all virtual reality experiences built with the React family of technologies on Oculus, including Oculus Home.
We acknowledge there are still blockers that prevent parts of the community from adopting Hermes and we are committed to building support for these missing features. Our goal is to be fully featured so that Hermes is the right choice for most React Native apps. Here is how the community has already shaped the Hermes roadmap:
Reflectwere originally excluded from Hermes because Facebook does not use them. We were also concerned that adding Proxy would hurt property lookup performance even when Proxy is not used. But Proxy quickly become the most requested feature of Hermes due to popular libraries such as MobX and Immer. We carefully evaluated and decided to build it just for the community, and we managed to implement it with very low cost. Since this is a feature we don’t use, we relied on our community to prove its stability. We started by testing Proxy behind a flag and created opt-in npm packages for release v0.4 and v0.5, and it’s enabled by default starting from v0.7.
Intl) was the second most requested feature.
Intlis a huge set of APIs and often requires the implementation to include 6MB worth of Unicode CLDR data. This is why polyfills like FormatJS (a.k.a.
react-intl) and JS engines like the international variant build of community JSC are so huge. To avoid substantially increasing the binary size of Hermes, we decided to implement it with another strategy by consuming and mapping the ICU facilities provided by the libraries included in the operating systems, at the cost of some (often minor) variance in behaviors across platforms.
Intlby default, so that’s what we did and it’s available starting from release v0.8.
Intlon iOS. Stayed tuned!
Array.prototype.sortamended in ES2019. This has been fixed and will be available in the next release.
Function.prototype.toStringimplementation caused performance to drop in libraries doing improper feature detection and blocked users from doing source code injecting. This helped us strengthen our stance that Hermes, whenever possible, should not get in the way of developers and to respect de-facto practices.
It’s extremely important for us to prepare the ecosystem for a smooth adoption. We encourage you to try out Hermes, and file issues on our GitHub repository for any feedbacks, questions, feature requests and incompatibilities.
We’d love to thank the Hermes team, the React Native team, and the many contributors from the React Native community for their work to improve Hermes.
I’d also love to personally thank (in alphabetic order) Eli White, Luna Wei, Neil Dhar, Tim Yung, Tzvetan Mikov, and many others for their help during the writing.