【webAssembly系列】webAssembly初探究竟

1、前言javascript

自從JavaScript誕生開始,到如今開始變成流行的編程語言,背後的是web發展所推進的。web應用的變得更多更復雜,可是漸漸暴露出JavaScript的問題:html

(1)語法太靈活致使開發大型web項目困難;前端

(2)性能不足知足一些場景的須要。java

 

2、爲何須要WebAssemblynode

針對以上的問題,JavaScript出現了一些代替語言,好比:react

(1)微軟的TypeScript經過JS加入靜態類型檢查機制來改進js鬆散的語法,提高代碼健壯性。webpack

(2)谷歌的Dart則是爲瀏覽器引入新的虛擬機去直接運行Dart程序以提高性能。c++

(3)火狐的asm.js則是取JS的子集,JS引擎針對asm.js作性能優化。git

以上嘗試各有優缺點。其中:github

(1)TypeScript只是解決JS語法鬆散的問題,最後仍是須要編譯成JS去運行,對性能沒有提高。

(2)Dart只能在chrome預覽版中運行,無主流瀏覽器支持,用Dart開發的人很少。

(3)asm.js語法太簡單,有很大限制,開發現率低。

三大瀏覽器巨頭分別提出了本身的解決方案,互補兼容,這違背了web的宗旨,是技術規範統一讓web走到今天,所以若是有一套新的規範去解決JS面臨的問題就太好了。

因而webAssembly出現了,webAssembly是一種新的字節碼格式,主流的瀏覽器已經支持webAssembly。和JS須要解釋執行不一樣的是,webAssembly字節碼和底層機器碼很類似可快速裝載運行,所以性能相對於JS解釋執行大大提高。也就是說webAssembly並非一門編程語言,而是一份字節碼標準,須要用高級語言編譯出字節碼放到webAssembly虛擬機中才能運行,瀏覽器廠商須要作的是根據webAssembly規範實現虛擬機。

 

3、webAssembly原理

要弄清楚webAssembly原理,須要先搞清計算機運行原理。

 

電子計算機是由電子元件組成,爲了方便處理電子元件只存在開閉兩種狀態,對於1和0,也就是計算機只認識1和0,數據和邏輯都須要由1和0表示,也就是能夠直接裝載到計算機中運行的機器碼。機器碼可讀性極差,所以人們經過高級語言c,c++,Java,Go等編寫再編譯成機器碼。

 

因爲不一樣的計算機CPU架構不一樣,機器碼標準有所差異,常見的CPU架構包括X86,AMD64,ARM,所以在由高級編程語言編程成可自行代碼時須要指定目標架構。

 

webAssembly字節碼是一種抹平了不一樣架構的機器碼,webAssembly字節碼不能直接在任何一種CPU架構上運行,可是因爲很是接近機器碼,能夠很是快的被翻譯爲對應架構的機器碼,所以webAssembly運行速度接近機器碼,這聽上去很是像java字節碼。

 

相對於JS,webAssembly有如下優勢:

(1)體積小。因爲瀏覽器運行時只加載編譯成的字節碼,同樣的邏輯比字符串描述的JS文件體積小不少。

(2)加載快。因爲文件體積小,再加上無需解釋執行,webAssembly能更快的加載並實例化,減小運行前的等待時間。

(3)兼容問題少。webAssembly是很是底層的字節碼規範,制定好之後不多變更,就算髮生變化,只須要從高級語言編譯成字節碼過程作兼容。可能出現兼容問題是JS和webAssembly橋接的JS接口。

 

每個高級語言都去實現源碼到不一樣平臺的機器碼的轉換工做是重複的,高級語言只須要生成底層虛擬機(LLVM)認識的中間語言(LLVM IR),LLVM能實現:

(1)LLVM IR到不一樣CPU架構機器碼的生成。

(2)機器碼編譯時性能和大小優化。

 

除此以外LLVM還實現了LLVM IR到webAssembly字節碼的編譯功能,也就是說只要高級語言能轉換成LLVM IR,就能編譯成webAssembly字節碼,目前能編譯webAssembly字節碼的高級語言有:

(1)AssemblyScript:語法和TypeScript一致,對前端來講學習成本低,爲前端編寫webAssembly最佳選擇。

(2)c\c++:官方推薦的方式,詳細見文檔

(3)Rust:語法複雜,學習成本高,對於前端來講可能會不適應。詳細見使用文檔

(4)kotlin:語法和java,js很類似,語言學習成本低,詳細見文檔

(5)GoLang:語法簡單,學習成本低。可是對於webAssembly還處於未正式發佈階段,詳細見文檔

 

一般負責把高級語言翻譯到 LLVM IR 的部分叫作編譯器前端,把 LLVM IR 編譯成各架構 CPU 對應機器碼的部分叫作編譯器後端; 如今愈來愈多的高級編程語言選擇 LLVM 做爲後端,高級語言只需專一於如何提供開發效率更高的語法同時保持翻譯到 LLVM IR 的程序執行性能。

 

4、編寫webAssembly

4.1 爲何選擇AssemblyScript做爲webAssembly的開發語言

AssemblyScript相對於C,rust等其餘語言去寫webAssembly而言,好處就是:對於前端來講無需額外的新語言學習成本,還有對於不支持webAssembly的瀏覽器,能夠經過TypeScript編譯器編譯成能夠正常執行的JS代碼。從而實現從JS到webAssembly的平滑遷移。

 

4.2接入webpack構建

任何新的web開發技術都少不了構建,爲了提供一套流暢的webAssembly開發流程,接下來開始介紹webpack具體步驟。

一、安裝依賴,以便TS源碼被AssemblyScript編譯成webAssembly。

{
  "devDependencies": { "assemblyscript": "github:AssemblyScript/assemblyscript", "assemblyscript-typescript-loader": "^1.3.2", "typescript": "^2.8.1", "webpack": "^3.10.0", "webpack-dev-server": "^2.10.1" } }

 

二、修改webpack.config.js,加入loader。

module.exports = { module: { rules: [ { test: /\.ts$/, loader: 'assemblyscript-typescript-loader', options: { sourceMap: true, } } ] }, };

 

三、修改typeScript的編譯器配置tsconfig.json,以便讓typeScript編譯器能支持AssemblyScript中引入內置類型和函數。

{
  "extends": "../../node_modules/assemblyscript/std/portable.json", "include": [ "./**/*.ts" ] }

 

四、配置直接繼承自AssemblyScript內置的配置文件。

 

5、webAssembly相關工具

除了上面提到的webAssembly二進制工具箱,webAssembly社區還有如下經常使用工具:

(1)Emscripten:能把c,c++代碼轉換成wasm,asm.js。

(2)Binaryen:提供更簡單的IR,把IR轉換成wasm,而且提供wasm的編譯時優化,wasm虛擬機,wasm壓縮等功能,前面提到的AssemblyScript就是基於他的。

 

6、webAssembly  JS API

目前webAssembly只能經過js去加載和執行,可是將來瀏覽器中能夠經過像加載JS那樣去加載和執行webAssembly,下面介紹如何使用JS調用webAssembly。

JS 調 WebAssembly 分爲 3 大步:加載字節碼 > 編譯字節碼 > 實例化,獲取到 WebAssembly 實例後就能夠經過 JS 去調用了,以上 3 步具體的操做是:

一、對於瀏覽器能夠經過網絡請求去加載字節碼,對於nodejs能夠經過fs模塊讀取字節碼文件;

二、在獲取到字節碼後都須要轉換成ArrayBuffer後才能被編譯,經過webAssembly經過JS API webAssembly.compile編譯後會經過Promise resolve 一個webAssembly.module,這個module是不能直接被調用的須要。

三、在獲取到module後須要經過webAssembly.instance API去實例化module,獲取到instance後就能夠像使用JS模塊一個調用。

 

其中的第 二、3 步能夠合併一步完成,前面提到的 WebAssembly.instantiate 就作了這兩個事情。

WebAssembly.instantiate(bytes).then(mod=>{
  mod.instance.f(50); })

 

7、webAssembly  調用 JS

以前的例子都是用 JS 去調用 WebAssembly 模塊,可是在有些場景下可能須要在 WebAssembly 模塊中調用瀏覽器 API,接下來介紹如何在 WebAssembly 中調用 JS。

WebAssembly.instantiate 函數支持第二個參數 WebAssembly.instantiate(bytes,importObject),這個 importObject 參數的做用就是 JS 向 WebAssembly 傳入 WebAssembly 中須要調用 JS 的 JS 模塊。舉個具體的例子,改造前面的計算斐波那契序列在 WebAssembly 中調用 Web 中的 window.alert 函數把計算結果彈出來,爲此須要改造加載 WebAssembly 模塊的 JS 代碼:

WebAssembly.instantiate(bytes,{
  window:{ alert:window.alert } }).then(mod=>{ mod.instance.f(50); })

對應的還須要修改 AssemblyScript 編寫的源碼:

// 聲明從外部導入的模塊類型 declare namespace window { export function alert(v: number): void; } function _f(x: number): number { if (x == 1 || x == 2) { return 1; } return _f(x - 1) + _f(x - 2) } export function f(x: number): void { // 直接調用 JS 模塊 window.alert(_f(x)); }

修改以上 AssemblyScript 源碼後從新用 asc 經過命令 asc f.ts 編譯後輸出的 wast 文件比以前多了幾行:

(import "window" "alert" (func $src/asm/module/window.alert (type 0))) (func $src/asm/module/f (type 0) (param f64) get_local 0 call $src/asm/module/_f call $src/asm/module/window.alert)

多出的這部分 wast 代碼就是在 AssemblyScript 中調用 JS 中傳入的模塊的邏輯。

除了以上經常使用的 API 外,WebAssembly 還提供一些 API,你能夠經過這個 d.ts 文件去查看全部 WebAssembly JS API 的細節。

 

8、不止於瀏覽器

webAssembly做爲一種底層字節碼,除了能在瀏覽器中運行外,還能在其餘環境下運行。

一、直接執行wasm二進制文件

前面提到的 Binaryen 提供了在命令行中直接執行 wasm 二進制文件的工具,在 Mac 系統下經過 brew install binaryen 安裝成功後,經過 wasm-shell f.wasm 文件便可直接運行

二、在nodejs中運行

目前 V8 JS 引擎已經添加了對 WebAssembly 的支持,Chrome 和 Node.js 都採用了 V8 做爲引擎,所以 WebAssembly 也能夠運行在 Node.js 環境中;

V8 JS 引擎在運行 WebAssembly 時,WebAssembly 和 JS 是在同一個虛擬機中執行,而不是 WebAssembly 在一個單獨的虛擬機中運行,這樣方便實現 JS 和 WebAssembly 之間的相互調用。

要讓上面的例子在 Node.js 中運行,可使用如下代碼:

const fs = require('fs'); function toUint8Array(buf) { var u = new Uint8Array(buf.length); for (var i = 0; i < buf.length; ++i) { u[i] = buf[i]; } return u; } function loadWebAssembly(filename, imports) { // 讀取 wasm 文件,並轉換成 byte 數組 const buffer = toUint8Array(fs.readFileSync(filename)); // 編譯 wasm 字節碼到機器碼 return WebAssembly.compile(buffer) .then(module => { // 實例化模塊 return new WebAssembly.Instance(module, imports) }) } loadWebAssembly('../temp/assembly/module.wasm') .then(instance => { // 調用 f 函數計算 console.log(instance.exports.f(10)) });

在 Nodejs 環境中運行 WebAssembly 的意義其實不大,緣由在於 Nodejs 支持運行原生模塊,而原生模塊的性能比 WebAssembly 要好。 若是你是經過 C、Rust 去編寫 WebAssembly,你能夠直接編譯成 Nodejs 能夠調用的原生模塊。

 

9、webAssembly展望

從上面的內容可見 WebAssembly 主要是爲了解決 JS 的性能瓶頸,也就是說 WebAssembly 適合用於須要大量計算的場景,例如:

(1)在瀏覽器中處理音視頻,flv.js 用 WebAssembly 重寫後性能會有很大提高;

(2)react的dom diff 中涉及到大量計算,用webAssembly重寫react核心模塊能提高性能。safari瀏覽器使用的JS引擎JavaScriptCore也已經支持webAssembly,RN應用性能也能提高。

(3)突破大型3D網頁遊戲性能瓶頸,白鷺引擎已經開始探索用 WebAssembly

 

10、總結

WebAssembly 標準雖然已經定稿而且獲得主流瀏覽器的實現,但目前還存在如下問題:

(1)瀏覽器兼容性很差,只有最新版本的瀏覽器支持,而且不一樣瀏覽器對JS webAssembly互相調的API支持不一致。

(2)生態工具不完善不成熟,目前還不能找到一門體驗流暢的編寫webAssembly的語言,都還處於初步階段。

(3)學習資料太少,還須要更多的人去探索去採坑。

總之如今的 WebAssembly 還不算成熟,若是你的團隊沒有不可容忍的性能問題,那如今使用 WebAssembly 到產品中還不是時候, 由於這可能會影響到團隊的開發效率,或者遇到沒法輕易解決的坑而阻塞開發。

 

11、參考

一、http://www.ruanyifeng.com/blog/2017/09/asmjs_emscripten.html

二、https://www.ibm.com/developerworks/cn/opensource/os-cn-clang/index.html

三、https://developer.mozilla.org/zh-CN/docs/WebAssembly/Understanding_the_text_format

四、https://developer.mozilla.org/zh-CN/docs/WebAssembly/Using_the_JavaScript_API

相關文章
相關標籤/搜索