The time it takes for the app to become usable, called time to interact (TTI)
The download size (on Android, APK size)
Metrics for MatterMost React Native app running on a Google Pixel, similar in performance to popular phones in markets like India.
Key Hermes architectural decisions
Mobile device limitations, such as smaller amounts of RAM and slower flash, led us to make certain architectural decisions. To optimize for this environment, we implemented the following:
The bytecode is designed so that at runtime, it can be mapped into memory and interpreted without needing to eagerly read the entire file. Flash memory I/O adds significant latency on many medium and low-end mobile devices, so loading bytecode from flash only when needed and optimizing bytecode for size leads to significant TTI improvements. In addition, because the memory is mapped read-only and backed by a file, mobile operating systems that don’t swap, such as Android, can still evict these pages under memory pressure. This reduces out-of-memory process kills on memory constrained devices.
Hermes today has no JIT compiler. This means that Hermes underperforms some benchmarks, especially those that depend on CPU performance. This was an intentional choice: These benchmarks are generally not representative of mobile application workloads. We have done some experimentation with JITs, but we believe that it would be quite challenging to achieve beneficial speed improvements without regressing our primary metrics. Because JITs must warm up when an application starts, they have trouble improving TTI and may even hurt TTI. Also, a JIT adds to native code size and memory consumption, which negatively affects our primary metrics. A JIT is likely to hurt the metrics we care about most, so we chose not to implement a JIT. Instead, we focused on interpreter performance as the right trade-off for Hermes.
Garbage collector strategy
On mobile devices, efficient use of memory is especially important. Lower-end devices have limited memory, OS swapping does not generally exist, and operating systems aggressively kill applications that use too much memory. When apps are killed, slow restarts are required and background functionality suffers. In early testing, we learned that virtual address (VA) space, especially contiguous VA space, can be a limited resource in large applications on 32-bit devices even with lazy allocation of physical pages.
To minimize memory and VA space used by the engine, we have built a garbage collector with the following features:
On-demand allocation: Allocates VA space in chunks only as needed.
Noncontiguous: VA space need not be in a single memory range, which avoids resource limits on 32-bit devices.
Moving: Being able to move objects means memory can be defragmented and chunks that are no longer needed are returned to the operating system.
Enabling improvements to React Native
React Native was our initial use case and has informed much of our work to date, but we aren’t stopping there. We intend to build time and memory profiling tools to make it easier for developers to improve their applications. We would like to fully support the Visual Studio Code debugger protocol, including completion and other features not available today. We’d also like to see other mobile use cases.