加載速度提升 15%,攜程對 RN 新一代 JS 引擎 Hermes 的調研


引言

Facebook在Chainreact2019大會上正式推出了新一代JavaScript執行引擎Hermes。Hermes是個輕量級的js引擎,專門對Android上運行reactNative進行了優化。我們第一時間在 CRN 項目中集成了Hermes, 並做了深度調研。

 

一、Hermes介紹

自ReactNative推出以來,有大量的APP接入並使用,其中也包括大型應用的主流程業務。隨着業務復雜度不斷上升,性能問題變得無法忽視。

在分析性能數據時,Facebook團隊發現 JavaScript 引擎是影響啟動性能和應用包體積的重要因素。由於JavaScriptCore最初是為桌面瀏覽器端設計,相較於桌面端,移動端能力有太多的限制,為了能從底層對移動端進行性能優化,Facebook團隊選擇自建JavaScrip引擎,設計了Hermes,限於iOS AppStore審核限制,目前僅用於Android平台。

Chain React大會上官方給出了Hermes引擎一組數據:

  • 從頁面啟動到用戶可操作的時間長短(Time To Interact:TTI),從4.3s減少到2.01s

  • App的下載大小,從41MB減少到22MB

  • 內存占用,從185MB減少到136MB

CRN先前做過框架代碼拆分和預加載、業務代碼懶加載、業務代碼預加載等性能優化方案,正困惑於如何更近一步進行性能優化。當看到Hermes這三個關鍵指標都有了顯著的提高,非常激動,覺得Hermes是非常好的一個方向,接下來我們就來了解Hermes的使用和實測性能數據。

 

二、快速上手Hermes

Faceback團隊已經將Hermes工具上傳到了npm : hermesvm 。hemres工具可以直接運行js代碼、轉換字節碼並且提供非常多的參數進行調優控制。

這里介紹一下hermesvm執行JS代碼和轉換bytecode功能。

// 創建hermes_test文件,內容:print("This is Hermes Demo"); vim hermes_test.js // 直接執行純文本js ~/node_modules/hermesvm/osx-bin/hermes hermes_test.js This is Hermes Demo // 轉換成bytecode ~/node_modules/hermesvm/osx-bin/hermes --emit-binary hermes_test.js -out hermes_test.hbc // 執行字節碼 ~/node_modules/hermesvm/osx-bin/hermes hermes_test.hbc This is Hermes Demo

 

三、Hermes是如何優化的?

主流JavaScript引擎,例如JSC、V8、SpiderMonkey等幾乎都是為了桌面端瀏覽器服務的,Hermes針對移動終端設備的特點做了一些優化,其中最重要的我們認為是以下兩點:

3.1 字節碼預編譯

現代主流的JavaScript引擎在執行一段js代碼的大概流程是:

  • 先讀取源碼文件

  • 解析源代碼並轉換成字節碼(bytecode)

  • 最后執行

在運行時解析源碼轉換字節碼是一種時間浪費,所以Hermes選擇預編譯的方式在編譯期間生成字節碼。這樣做一方面避免了不必要的轉換時間,另一方面多出的時間可以用來優化字節碼,從而提高執行效率。

3.2 放棄JIT

為了加快執行效率,現在主流的JavaScript引擎都會使用一個JIT編譯器在運行時通過轉換成機器碼的方式優化JS代碼。Faceback團隊認為JIT編譯器有主要倆個問題:

  • 要在啟動時候預熱,對啟動時間有影響;

  • 會增加引擎size大小和運行時內存消耗;

基於這倆點對性能指標的影響,Faceback團隊決定不實現JIT編譯器。

這里所謂放棄JIT,有兩點需要再解釋一下:

  • 純文本JS代碼執行效率降低。放棄JIT,是指放棄運行時Hermes引擎對純文本JS代碼的編譯優化。我們的驗證數據也表面,純文本的JS代碼執行,Hermes引擎明顯比JavaScriptCore慢。

  • 對RN代碼的動態性無影響。由於Hermes仍然可以執行純文本的JS代碼,並且可以支持動態讀取bytecode, 因此對RN的動態性並無影響。

 

四、如何集成Hermes?

4.1 從新創建工程集成

 
        
1. 升級最新react-native-cli npm install -g react-native-cli 2.初始化最新react-native工程,最新版為0.60.3 react-native init HermesDemo 3. 開啟hermes, 編輯HermesDemo工程 android/app/build.gradl文件 project.ext.react = [ entryFile: "index.js", - enableHermes: false // clean and rebuild if changing + enableHermes: true // clean and rebuild if changing ] 4. 使用Relase包體驗Hermes帶來的速度提升 react-native run-android --variant release

 

4.2 從源碼集成

 
        
git clone https://github.com/facebook/react-native.git // 需要切換到Hermes release節點,比如:eec4dc6 cd react-native npm install ./gradlew :RNTester:android:app:installHermesRelease // 使用生產環境hermes

 

4.3 Hermes集成過程分析

分析react-native react.gradle源碼可以看到,如果打開了Hermes開關,會在原先打包RN代碼的bundleXXXJsAndAsset task后面追加執行一段Hermes轉換命令: hermes --emit-binary -out xxx。

...
// 1. 執行標准RN打包 commandLine(*nodeExecutableAndArgs, cliPath, bundleCommand, "--platform", "android", "--dev", "${devEnabled}", "--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir, "--sourcemap-output", jsPackagerSourceMapFile, *extraArgs) ... ... // 2. 將打包好的jsbundle文件轉換成字節碼 if (enableHermes) { commandLine(getHermesCommand(), "-emit-binary", "-out", jsBundleFile, jsBundleFile, *hermesFlags) } ...

 

4.4 執行過程分析

為了進一步抽象JavaScript執行層,RN底層創建了JSExecutor和Runtime接口,並把大部分業務邏輯放到了實現了JSExecutor的JSIExcutor.cpp中。對於JavaScript執行引擎來說只需要實現Runtime接口即可對接RN框架。

JavaScriptCore的Runtime實現類是JSCRuntime。相應的,此次Hermes升級,底層創建了HermesRuntime。

 
        
// JSCRuntime.cpp jsc Runtime
class JSCRuntime : public jsi::Runtime

// hermes.h hermes Runtime
class HermesRuntime : public jsi::Runtime...

每一種JSExecutor都提供了創建類XXXExecutorFactory來創建相應實例,並且提供了相應的Java對象。

RN框架在初始化ReactInstanceManager的時候需要傳入JavaScriptExecutorFactory。如果要切換JavaScript執行引擎只需要在ReactInstanceManager創建的時候做控制即可。

官方的控制流程是,優先加載jscexecutorso,如果成功則使用JSCRuntime,否則使用HermesRuntime。

 
        
private JavaScriptExecutorFactory getDefaultJSExecutorFactory(String appName, String deviceName) { try { // If JSC is included, use it as normal SoLoader.loadLibrary("jscexecutor"); return new JSCExecutorFactory(appName, deviceName); } catch(UnsatisfiedLinkError jscE) { // Otherwise use Hermes return new HermesExecutorFactory(); } }

由此可見無論是對於RN JS代碼的打包還是Native代碼邏輯的更改,升級Hermes的成本都非常低。

資源搜索網站大全 https://www.renrenfan.com.cn 廣州VI設計公司https://www.houdianzi.com

五、Hermes,JavaScriptCore,V8 的對比

通過上面的Hermes集成分析可知,Hermes對整個RN原有架構的侵入是極少的,甚至做到了可插拔式接入。我們很快將Hermes集成到攜程CRN框架,並和原先的JavaScriptCore引擎以及社區提供的V8引擎做了比較。

經過我們的數據驗證,Faceback團隊提出的關鍵性指標相較於原先的JSC都有了顯著提高。

  • 首屏渲染速度:bytecode代碼執行情況下,Hermes比JavaScriptCore要快。在攜程App中,拿門票業務做了驗證,在做了預加載的情況下,首屏加載速度依然可以提升約15%。而V8的表現就非常糟糕了。

  • Native so size:RN所依賴的必要so庫,Hermes比JavaScriptCore減少了約16%(單armeabi架構壓縮后降低了0.5M左右),V8則要遠大於Hermes和JavaScriptCore。

  • 內存:拿 RNTester 工程測試進入RN頁面滑動進入若干頁面並退出之后,內存的波動情況比較可以看到,V8和Hermes內存增長要更加平滑。

  • CPU:拿 RNTester 工程測試進入RN頁面滑動進入若干頁面並退出之后,對比CPU波動情況。Hermes明顯好於V8和JavaScriptCore。 

 

六、Hermes引擎的動態性

另外通過我們的測試,Hermes在執行字節碼和文本JS上有一些很有意思的特性,這些特性讓升級成本變得非常低:

  • Hermes支持執行純文本的js

  • 支持動態加載純文本js或者bytecode

  • 支持bytecode和純文本js混合使用:比如a.hbc是bytecode,模塊中引用了b.js,b模塊是純文本js。在加載的時候可以先加載a.hbc文件,然后加載b.js文件。可正常執行。

  •  

七、Hermes目前的問題

Hermes諸多優點讓我們團隊非常興奮,幾乎覺得應該立馬把JavaScriptCore下掉,更換至Hermes。但隨着測試和集成的進行,Hermes帶來的問題逐漸顯現。

7.1 bytecode文件占用size過大問題

Hermes編譯的字節碼文件比純文本js文件增大100%。

攜程旅行App的安裝包中有20MB(7z壓縮后)左右的RN業務代碼,如果都編譯成bytecode,將會再增加20MB大小,這是無法接受的。另外,動態下發RN增量包時,由於是二進制文件diff,差分效率極低。

為了解決這個問題,我們根據Hermes的特性,轉變思路,將Hermes的bytecode編譯放到客戶端去做,客戶端同時存儲js和bytecode文件,如果有bytecode編譯完成則使用Hermes,否則仍然使用JavaScriptCore。

Hermes開源項目提供了編譯bytecode的complieJS方法,但這部分代碼沒有默認打包到RN的Hermes引擎中,我們稍加整合、封裝,通過JNI暴露出來,供業務使用。

拿最大的RN業務包(1100個文件,6.5MB大小),做測試,后台線程執行,小米9 Android10耗時2.49秒;三星S6edge+ android 7.0 耗時6秒。由於bytecode不是必須,因此該耗時尚可接受。

7.2 執行純文本js耗時長

在客戶端將純文本js轉換成bytecode之前,我們讓Hermes加載純文本。但實際測試下來,發現Hermes加載純文本的性能比JavaScriptCore要慢將近30%。主要原因是Hermes刪除JIT功能,致使對純文本js代碼運行變慢。

7.3緩存問題

我們對原生RN框架做了大量的優化,緩存使用過的JS執行引擎是優化過程非常重要的一環。

拿門票頁面舉例來說,如果用戶啟動App,第一次進入門票業務將會使用一個全新的JavaScript引擎並從磁盤讀取文件、加載文件、執行JS代碼。用戶退出門票頁面之后該引擎被緩存,如果用戶再一次進入將會使用緩存的引擎,不用重新讀取、加載和執行,僅僅需要創建相關JS對象並渲染即可。

遺憾的是,測試Hermes的緩存的時候,我們發現使用緩存的Hermes引擎加載業務代碼表現非常一般,甚至某些情況下比第一次加載還要慢。而使用緩存的JavaScriptCore引擎,第二次打開頁面的速度與打開純native頁面的速度幾乎相當,並且表現相當穩定。

為什么使用緩存的Hermes引擎打開頁面速度不理想,可能和Hermes的設計有關,我們還在進一步分析中。

 

八、總結與展望

  • 從目前情況來看,在解決緩存問題之前,我們無法在線上版本直接引入Hermes。

  • 解決緩存問題之后,可以采用JavaScriptCore+Hermes雙引擎。通過客戶端轉換bytecode字節碼。使用jsc加載優化之前的純文本js,一旦優化完畢切換至Hermes引擎。

  • 另外如果使用Hermes引擎我們需要充分測試穩定性和兼容性。

  • Hermes通過預編譯字節碼的方式提升js執行速度,給了我們新的思路。我們也正在調研JavaScriptCore或者V8的bytecode在移動端的支持度,性能和兼容性。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM