首頁>Club>
10
回覆列表
  • 1 # 勿忘養車

    WebAssembly讓你的Javascript計算效能提升70%

      現在的JavaScript程式碼要進行效能最佳化,通常使用一些常規手段,如:延遲執行、預處理、setTimeout等非同步方式避免處理主執行緒,高大上一點的會使用WebWorker。即使對於WebWorker也僅僅是解決了阻塞主執行緒的問題,但是對於JavaScript計算效能慢的問題並沒有解決。這裡對一些需要密集計算的場景我給大家推薦一個神器——WebAssembly。在目前階段,WebAssembly 適合大量密集計算、並且無需頻繁與 JavaScript 及 DOM 進行資料通訊的場景。比如遊戲渲染引擎、物理引擎、影象音訊影片處理編輯、加密演算法等

    WebAssembly是什麼?

      WebAssembly是一種執行在現代網路瀏覽器中的新型程式碼並且提供新的效能特性和效果。它設計的目的不是為了手寫程式碼而是為諸如C、C++和Rust等低階源語言提供一個高效的編譯目標。WebAssembly的模組可以被匯入的到一個網路app(或Node.js)中,並且暴露出供JavaScript使用的WebAssembly函式。JavaScript框架不但可以使用WebAssembly獲得巨大效能優勢和新特性,而且還能使得各種功能保持對網路開發者的易用性。這是來自MDN的介紹。但你是不是看了官方介紹也不知道WebAssembly到底是個什麼東西呢,沒關係開始我也這麼覺得。簡單來說WebAssembly就是瀏覽器提供的一項直接執行二進位制機器程式碼的能力。這些機器程式碼怎麼來呢,是透過C、C++或Rust等語言編譯來的。

      那麼如何編譯呢,首先你得學會寫C語言程式碼,然後你得用一系列工具把它編譯成二進位制程式碼。這個過程絕對不會是一帆風順的,因為根據我摸爬滾打的經驗來說,這玩意兒從頭到尾都是坑。WebAssembly的編譯過程需要用到以下工具:

    EmscriptenBinaryenWabt

      哦對了,還要裝Visual Studio2015,千萬別看這vs17新就裝了個17,因為Emscripten目前跟vs15的結合性最好。

    安裝工具鏈

      在所有工具鏈之前,需要安裝下面幾個工具:

    GitCMakeVisual Studio Community 2015 with Update 3 or newerPython 2.7.x

      然後下載編譯Emscripten,這玩意兒需要FQ,然後特別大,得慢慢等個半小時差不多,然後千萬別按照它官網的介紹來安裝,要按照MDN上的方式來安裝,這樣安裝下來直接帶有Binaryen了(我只會Windows平臺的配置):

    git clone https://github.com/juj/emsdk.gitcd emsdk # on Linux or Mac OS X ./emsdk install --build=Release sdk-incoming-64bit binaryen-master-64bit ./emsdk activate --global --build=Release sdk-incoming-64bit binaryen-master-64bit # on Windows emsdk install --build=Release sdk-incoming-64bit binaryen-master-64bit emsdk activate --global --build=Release sdk-incoming-64bit binaryen-master-64bit

      先克隆,克隆之後開啟資料夾,執行裡面的emcmdprompt.bat,開啟的命令列裡面可以執行install和active命令:

    emsdk install --build=Release sdk-incoming-64bit binaryen-master-64bit emsdk activate --global --build=Release sdk-incoming-64bit binaryen-master-64bit

      然後新增幾個環境變數:

    D:\emsdk-portable\binaryen\master_vs2015_64bit_binaryen\bin;d:\emsdk-portable;d:\emsdk-portable\clang\fastcomp\build_incoming_vs2015_64\Release\bin;d:\emsdk-portable\node\4.1.1_64bit\bin;d:\emsdk-portable\python\2.7.5.3_64bit;d:\emsdk-portable\java\7.45_64bit\bin;d:\emsdk-portable\emscripten\incoming;d:\emsdk-portable\binaryen\master;

      在實際的執行中你可能遇到這個錯誤:

    CMake does not find Visual C++ compiler

      那麼你需要新建一個Vs15的c++工程,按照這裡說的執行一下:Stack Overflow

      I have found the solution. While Visual Studio IDE installed successfully it did not install any build tools and therefore did not install the C++ compiler. By attempting to manually create a C++ project in the Visual Studio 2015 GUI I was able to prompt it to download the C++ packages. Cmake was then able to find the compiler without any difficulty.

      這樣一些跟WebAssembly相關的常見命令就可以運行了,本文的不詳細解釋WebAssembly,只說一些踩過的坑,具體比較詳細的我認為這篇文章很不錯——WebAssembly 實踐:如何寫程式碼。裡面的程式碼在這裡——wasm-examples.我們來看一些效能比較,大家同時執行斐波那契數列:

    // JavaScript版本function () { function fib (n) { if (n < 2) { return 1 } return fib(n - 2) + fib(n - 1) } return { fib } }// C版本int fibonacci (int n, int a, int b) { if (n <= 2) { return b; } return fibonacci(n - 1, b, a + b); }int fib (int n) { return fibonacci(n, 1, 1); }

      那麼它們的效能對比如下:

      一般來講斐波那契數列計算到40已經是很大的運算量了,可以看出由C直接轉化成wasm二進位制機器碼的計算效能比純原生js快了接近70%。有的同學可能會覺得這裡應該使用尾遞迴最佳化,我做過實驗,尾遞迴最佳化的js在計算40時,差不多是幾毫秒,但是同樣尾遞迴最佳化的c編譯成wasm幾乎是0。

    WebAssembly實際應用

      通常來講在普通的前端業務中根本不需要使用WebAssembly,但是在一些需要極大的計算能力的場景,比如Web地圖、WebAR、Web影象識別中傳統方案下js的計算效能無法達到要求,那麼這時候就是WebAssembly展現應用能力的時候了。對我們來說在實際中最有用的方式不是簡單計算一個數值,而是希望用c來處理一批資料,然後在JavaScript側能夠透過ArrayBuffer形式使用。對此百度地圖團隊有一篇文章——地圖引擎使用WebAssembly的經驗分享(WebGL地圖引擎技術番外篇之二)專門介紹了它們的一個使用場景。

      這裡呢它們提供了一種實踐路線:

    方案三:C/C++編譯 目前主流方案是使用 Emscripten 將 c/c++ 程式碼編譯成 asm.js 或 wasm。一開始沒有使用 emscripten 主要是調研期間遇到了兩個問題: ONLY_MY_CODE 模式編譯出的 asm.js 程式碼不是合法的 asm.js 程式碼 emscripten 預設的編譯模式會將一些依賴庫程式碼以及載入 asm.js 的程式碼一起編譯出來,這對於我們這種只需要一個 asm.js 模組的需求來說是不必要的。emscripten 有ONLY_MY_CODE模式可以選擇,顧名思義,這個模式就是隻會編譯出模組程式碼而不會增加其他多餘程式碼。但是在調研過程中發現這個模式編譯出的 asm.js 程式碼的型別標註有問題,導致程式碼不合法。 解決方案:官方 github 給出的解答是 ONLY_MY_CODE 模式沒有經過嚴格的測試,可以使用 SIDE_MODULE 選項來達到類似的效果。經過測試這個模式雖然也會增加少量額外程式碼但是可以解決問題。 emscripten 直接編譯 wasm 要求分配記憶體大於 16MB emacripten 加上-s WASM=1可以支援直接編譯 wasm,但是強制要求分配的記憶體大於16MB,這對於一些不需要這麼大記憶體的模組來說會造成浪費。 解決方案:放棄使用 emscripten 編譯 wasm,現在採用的編譯步驟是: 使用 emscripten 將 c++ 程式碼編譯成 asm.js 使用 binaryen 將 asm.js 編譯成與 wasm 等價的文字格式 wat 程式碼 使用 wabt 將 wat 程式碼編譯成二進位制 wasm 解決了這兩個問題之後,採用此方案就可以達到寫一次程式碼編譯同時得到 asm.js 和 wasm 的目的了。

      然而很不幸,這條路在我實踐過程中走不通,無論怎樣Emscripten都無法產出純淨的asm程式碼,總是會帶有一些膠水程式碼。比如C程式碼:

    // 分配記憶體,此陣列佔0x1000*4=16384bytestatic int s_array[0x1000];static int s_current_index = 0;int* get_start();int* get_end();void generate_array(int);// 暴露給JS使用,得到陣列的開始偏移量int* get_start() { return s_array; }// 暴露給JS使用,得到陣列的結束偏移量int* get_end() { return &s_array[s_current_index]; }// 將生成的陣列放進記憶體中void generate_array(int count) { for (int i = 0; i < count; ++i) { s_array[i] = i; } s_current_index = count; }

      最終經過Emscripten編譯成的Asm.js是如下程式碼:

    View Code

      往後用Binaryen這一步總是不成功。而後經過我不斷探索,發現了另一個神器:WebAssembly Explorer。他是一個線上的wasm編譯器,能夠很完美編譯出我們想要的wasm,而且沒有膠水程式碼。

      這裡C程式碼的環境最好選擇C99,這樣編譯出來的函式名跟你C模組中的函式名是一致的,否則會有一些不一樣。

      然後我們可以在頁面使用這個模組:

    <html> <head> <meta charset="UTF-8"> <title>Game of Life</title> </head> <body> <canvas></canvas> <script> // 透過fetch獲取wasm模組 fetch("./test.wasm").then(function (response) { return response.arrayBuffer(); }).then(function (bytes) { // 初始化記憶體,1個單位代表64kb=65536byte var memory = new WebAssembly.Memory({initial: 1, maximum: 1}); WebAssembly.instantiate(bytes, { env: { // memory 例項 // memory: memory, // table 例項 table: new WebAssembly.Table({ initial: 0, maximum: 0, element: "anyfunc" }), // 以下都是編譯生成的wasm所需要的變數,不需要可以直接傳0 abortStackOverflow: function () {}, DYNAMICTOP_PTR: 0, tempDoublePtr: 0, ABORT: 0, STACKTOP: 0, STACK_MAX: 0, gb: 0, fb: 0, memoryBase: 0, tableBase: 0 }, global: { NaN: NaN, Infinity: Infinity } }).then(function (results) { // 得到編譯後的wasm模組例項 var module = results.instance.exports; // 呼叫模組函式,生成從0到99的陣列 module.generate_array(100); // 透過slice偏移量得到最終的生成的陣列 var generatedArray = new Int32Array(module.memory.buffer).slice(module.get_start() >> 2, module.get_end() >> 2); console.log(generatedArray); }); }); </script> </body> </html>

      然後呢我們在控制檯得到如下結果:

      這裡需要注意的是,如果你按照百度文章裡面的程式碼來寫,你是跑不出這個結果,為什呢,我覺得百度這位同學估計也沒理解好WebAssembly的memory這個概念。

    var generatedArray = new Int32Array(memory.buffer).slice(module._get_start() >> 2, module._get_end() >> 2);

      這行是它的文章中的程式碼,這裡使用的memory.buffer根本不是wasm這個模組module的記憶體。

      這裡應當使用的是module.memory.buffer!!!!!!!!!!!!!!!

      這裡應當使用的是module.memory.buffer!!!!!!!!!!!!!!!

      這裡應當使用的是module.memory.buffer!!!!!!!!!!!!!!!

    WebAssembly的相容性

      總的來說這個新特性的發展前景是比較好的,頭一次由所有的瀏覽器廠商都打成一致意見。而且未來還會讓我們在裡面操作DOM。目前階段來說,看下圖嘍

    參考資料

    下面就是我搜集的跟WebAssembly相關的一些比較好的資料(作為一個技術我最煩那些只講技術歷史不講實際內容的文章)

    設計文件https://github.com/WebAssembly/design webassembly和webworkerhttps://stackoverflow.com/questions/47083951/how-to-use-webassembly-wasm-code-in-a-web-worker wasm webworker js效能測試比較案例https://tech.foodora.com/webassembly/https://github.com/eliamaino-fp/webassembly-js/issues wasm社群http://webassembly.org/docs/semantics/ emscriptenhttps://kripken.github.io/emscripten-site/docs/api_reference/fetch.html 傳遞js陣列作為wasm函式的引數https://stackoverflow.com/questions/41875728/pass-a-javascript-array-as-argument-to-a-webassembly-function wasm examplehttps://github.com/mdn/webassembly-examples 另外wasm例子https://github.com/Hanks10100/wasm-examples wasm MDNhttps://developer.mozilla.org/en-US/docs/WebAssembly/Concepts wasm實踐——segment faulthttps://segmentfault.com/a/1190000008402872#articleHeader20 wasm canvas demohttps://github.com/cunzaizhuyi/wasm-canvasDemo

  • 中秋節和大豐收的關聯?
  • 化工車間主任的職責?