개발자라면 누구나 성능에 대한 고민을 한다. 특히 크로스 플랫폼 개발에서는 네이티브 앱과 비교했을 때 성능 차이가 항상 아킬레스건이었다. React Native는 JavaScript를 사용해 iOS와 Android 앱을 동시에 개발할 수 있는 혁신적인 프레임워크지만, 그동안 성능 문제로 많은 개발자들의 한숨을 자아냈다.
하지만 2019년, Meta(당시 Facebook)가 Hermes라는 새로운 JavaScript 엔진을 공개하였고(오픈소스화는 2022년에 했습니다.) 처음엔 욕을 엄청 먹었지만, 안정화가 되면서 점차 React Native의 판도가 완전히 바뀌었다. 이제 React Native 앱은 Swift나 Kotlin으로 개발된 네이티브 앱과 견줄만한 성능을 보여준다. 어떻게 이런 변화가 가능했을까?!
React Native 앱은 원래 JavaScriptCore(JSC)라는 JavaScript 엔진을 사용했다. 이 엔진은 iOS에 기본 탑재된 엔진으로, Android에서도 동일한 경험을 제공하기 위해 React Native에 포함되었다. 하지만 JSC는 모바일 환경에 최적화되지 않았고, 특히 런타임에 JavaScript를 컴파일하는 방식인 JIT 즉, Just In Time 컴파일 하는 방식으로 인해 앱 시작 시간이 느리다는 큰 단점이 있었다.
JSC의 특징 도식화
Meta는 Facebook Marketplace 앱을 React Native로 개발하면서 이러한 문제점을 직접 경험했고, 결국 모바일에 최적화된 새로운 JavaScript 엔진의 필요성을 느꼈다. (문제를 체감하자마자, 틀을 뜯어고친다는 게 정말 세계적인 기업인 이유가 있지 않나 싶다.)
Hermes는 모바일 환경에 최적화된 오픈소스 JavaScript 엔진이다. 가장 큰 특징은 다음과 같다:
(👨🏻🏫 : 간단히 말하자면, 앱이 실행되기 전에 어려운 숙제를 미리 다 해놓는 똑똑한 엔진이라고 파악하면 좋지 않을까요?)
Hermes의 도입으로 인한 성능 개선은 실로 놀랍다. Reddit의 한 개발자는 JSC에서 Hermes로 전환한 후 앱 시작 시간이 12.9초에서 3.9초로 70% 단축되었다고 보고했다. 이는 특히 저사양 Android 기기에서 더욱 두드러진 효과를 보인다.
Our app is 70% faster to load with Hermes!
Meta의 내부 측정에 따르면 Hermes는 다음과 같은 개선을 가져왔다:
| 측정 항목 | JSC | Hermes | 개선율 |
|---|---|---|---|
| 앱 시작 시간(TTI) | 더 느림 | 더 빠름 | 최대 70% 향상 |
| APK 크기 | 더 큼 | 더 작음 | 크기 감소 |
| 메모리 사용량 | 더 많음 | 더 적음 | 메모리 효율성 증가 |
Hermes가 이러한 성능 향상을 가져올 수 있는 비결은 AOT 컴파일 방식에 있다. 기존 JSC는 앱이 실행될 때 JavaScript 코드를 해석하고 컴파일하는(JIT) 과정을 거쳤지만, Hermes는 앱 빌드 시점에 JavaScript를 효율적인 바이트코드로 미리 변환한다.
기존의 JSC 엔진의 파이프라인

현재 Hermes 엔진의 파이프라인

이 방식은 다음과 같은 이점을 제공한다:
(👨🏻🏫 : 런타임 과정에 실행하는 작업이 많이 줄어든 것을 볼 수 있죠? 마치 수학 시험을 볼 때, 공식을 외우지 않아, 그 자리에서 공식을 증명하는 것과, 시험 전에 공식을 미리 외워가는 차이와도 같답니다. 외워두기만 하면 시험장에서 공식을 유도할 필요 없이 바로 문제를 풀 수 있죠!)
Hermes는 처음에 GenGC(Generational Garbage Collector)라는 가비지 컬렉터를 사용했다. 하지만 GenGC는 단일 스레드에서 작동하며 메인 JavaScript 스레드에서 실행되어 긴 Garbage Collector 내에서 일시 정지(pause) 현상을 일으켰다 Facebook의 관찰에 따르면 이 일시 정지는 평균 200ms, 최악의 경우 1.4초, 때로는 7초까지 지속되었다
Facebook의 관찰, Garbage Collector 내에서 일시 정지(pause) 현상 관측.
이러한 GenGC의 한계를 극복하기 위해 Meta는 2021년 React Native 0.65 버전에서 Hades라는 새로운 동시성 가비지 컬렉터를 도입했다
(👨🏻🏫 : 는 추후에 블로그로 더 자세히 다뤄보겠습니다..!!)
Using Hermes in React Native - LogRocket Blog
React Native 0.70 버전부터 Hermes는 기본 JavaScript 엔진으로 설정되어 있다. 하지만 이전 버전을 사용하거나 명시적으로 설정하고 싶다면 다음과 같이 할 수 있다:
// android/app/build.gradle 파일에 추가
project.ext.react = [
entryFile: "index.js",
enableHermes: true // Hermes 활성화
]
// 출처: LogRocket Blog
iOS에서는 React Native 0.64 버전부터 Hermes를 사용할 수 있게 되었다. Podfile에 다음과 같이 설정한다:
*# ios/Podfile 파일에 추가*
use_react_native!(
:path => config[:reactNativePath],
:hermes_enabled => true *# Hermes 활성화*
)
// 출처: LogRocket Blog
Expo에서는 Hermes가 기본 JavaScript 엔진으로 설정되어 있다. 특정 플랫폼에서만 Hermes를 사용하거나 비활성화하려면 app.json 파일에서 설정할 수 있다:
{
"expo": {
"jsEngine": "hermes",
"ios": {
"jsEngine": "jsc" *// iOS에서만 JSC 사용*
}
}
}
*// 출처: Expo 공식 문서*
Hermes는 대부분의 JavaScript 기능을 지원하지만, 일부 라이브러리나 기능은 호환성 문제가 있을 수 있다. 특히:
Expo를 사용하는 경우, Hermes 바이트코드 형식은 버전마다 달라질 수 있으므로 runtimeVersion을 적절히 관리해야 한다. React Native 버전을 업데이트할 때는 Hermes 버전도 함께 업데이트되므로 주의해야 한다.
Meta는 Hermes를 지속적으로 개선하고 있다. 최근에는 다음과 같은 발전이 있었다:
Hermes의 도입으로 React Native는 Swift나 Kotlin으로 개발된 네이티브 앱과 견줄만한 성능을 제공하게 되었다. 이는 크로스 플랫폼 개발의 가장 큰 단점이었던 성능 문제를 크게 개선한 것으로, React Native의 경쟁력을 한층 강화했다.
Hermes의 등장은 React Native 생태계에 새로운 활력을 불어넣었다. 이제 개발자들은 JavaScript의 생산성과 네이티브 앱의 성능을 동시에 누릴 수 있게 되었다. 앞으로 Hermes가 React Native 생태계에 어떤 혁신을 더 가져올지 기대된다. 아무래도 오픈소스니까, 많은 이들의 참여를 통해 보다 더 빠르게 성장할 것이다.
🙇🏻 글 내에 틀린 점, 오탈자, 비판, 공감 등 모두 적어주셔도 됩니다. 감사합니다..! 🙇🏻