WebAssembly使用方法研究
序
之前在瀏覽前端技術(shù)的時(shí)候留意到了webAssembly這項(xiàng)技術(shù),看到它是類似于在前端調(diào)用后端語言的源碼或者說功能,再加上筆者淺學(xué)過C++和java,故在此做一些調(diào)研和嘗試
什么是WebAssembly
WebAssembly以下簡稱WASM,通過將傳統(tǒng)意義上的后端語言(C、C++、Java、Rust等)編譯成字節(jié)碼,.wasm格式文件,在瀏覽器上調(diào)用解釋器編譯成機(jī)器碼才能運(yùn)行,因此WASM并不能真正達(dá)到匯編語言級(jí)別的性能,與Java比較像,都是編譯成中間字節(jié)碼,然后交由解釋器工作(在Java中則是JVM)。
2015年4月,WebAssembly Community Group 成立;
2015年6月,WebAssembly第一次以WCG的官方名義向外界公布;
2016年8月,WebAssembly開始進(jìn)入了漫長的“Browser Preview”階段;
2017年2月,WebAssembly官方LOGO在Github上的眾多討論中被最終確定;同年同月,一個(gè)歷史性的階段,四大瀏覽器(FireFox、Chrome、Edge、WebKit)在WebAssembly的MVP(最小可用版本)標(biāo)準(zhǔn)實(shí)現(xiàn)上達(dá)成共識(shí),這意味著WebAssembly在其MVP標(biāo)準(zhǔn)上的“Brower Preview”階段已經(jīng)結(jié)束;
2017年8月,W3C WebAssembly Working Group 成立,意味著WebAssembly正式成為W3C眾多技術(shù)標(biāo)準(zhǔn)中的一員。
2019年12月,WebAssembly成為萬維網(wǎng)聯(lián)盟(W3C)的推薦標(biāo)準(zhǔn),與HTML,CSS和JavaScript一起成為Web的第四種語言。
使用方法
從.wasm源文件到實(shí)例化的對(duì)象主要有三個(gè)步驟,加載->編譯->實(shí)例化->調(diào)用。
加載:讀取.wasm字節(jié)碼到本地中,一般是通過請(qǐng)求從網(wǎng)絡(luò)中取得。
編譯:在Worker線程進(jìn)行,編譯成平臺(tái)相關(guān)的代碼。
實(shí)例化:將宿主環(huán)境的一些對(duì)象、方法導(dǎo)入到wasm模塊中,比如導(dǎo)入操作dom的方法。
調(diào)用:通過上一步已經(jīng)實(shí)例化的對(duì)象,來調(diào)用wasm模塊中的方法。主要有兩種類型的API,一種是js提供的api,另一種是Web提供的api,Web提供的api支持流式編譯實(shí)例化。
js的方法,WebAssembly.instantiate(bufferSource,importObject),可以完成編譯和實(shí)例化。
bufferSource是含有效Wasm模塊二進(jìn)制字節(jié)碼的ArrayBuffer或TypedArray對(duì)象。importObject是要導(dǎo)入到Wasm模塊中的對(duì)象。方法在調(diào)用后返回一個(gè)Promise對(duì)象,resolve后返回一個(gè)對(duì)象,該對(duì)象包含編譯好的module和已經(jīng)實(shí)例化的instance,模塊導(dǎo)出的方法可以通過instance對(duì)象進(jìn)行調(diào)用。
web的方法,WebAssembly.instantiateStreaming(source,importObject)。不同之處在于第一個(gè)參數(shù),這里的source指的是尚未Resolve的Response對(duì)象(window.fetch調(diào)用后會(huì)返回該對(duì)象),好處就是可以邊讀取.wasm字節(jié)流,邊進(jìn)行編譯。其他參數(shù)和返回值和js的api均一致。
jsAPI嘗試
先簡單的嘗試一下,我們直接構(gòu)造一個(gè)wasm模塊的TypedArray對(duì)象,該模塊包含了一個(gè)add方法,然后調(diào)用WebAssembly.instantiate進(jìn)行編譯和實(shí)例化。對(duì)應(yīng)的C++代碼。
#include <emscripten.h> extern "C" { EMSCRIPTEN_KEEPALIVE int add(int a, int b) { return a + b; } } 對(duì)應(yīng)的.wasm字節(jié)碼: 00 61 73 6D 01 00 00 00 01 17 05 60 00 01 7F 60 00 00 60 01 7F 00 60 01 7F 01 7F 60 02 7F 7F 01 7F 03 07 06 01 04 00 02 03 00 04 05 01 70 01 02 02 05 06 01 01 80 02 80 02 06 0F 02 7F 01 41 90 88 C0 02 0B 7F 00 41 84 08 0B 07 82 01 09 06 6D 65 6D 6F 72 79 02 00 19 5F 5F 69 6E 64 69 72 65 63 74 5F 66 75 6E 63 74 69 6F 6E 5F 74 61 62 6C 65 01 00 03 61 64 64 00 01 0B 5F 69 6E 69 74 69 61 6C 69 7A 65 00 00 10 5F 5F 65 72 72 6E 6F 5F 6C 6F 63 61 74 69 6F 6E 00 05 09 73 74 61 63 6B 53 61 76 65 00 02 0C 73 74 61 63 6B 52 65 73 74 6F 72 65 00 03 0A 73 74 61 63 6B 41 6C 6C 6F 63 00 04 0A 5F 5F 64 61 74 61 5F 65 6E 64 03 01 09 07 01 00 41 01 0B 01 00 0A 30 06 03 00 01 0B 07 00 20 00 20 01 6A 0B 04 00 23 00 0B 06 00 20 00 24 00 0B 10 00 23 00 20 00 6B 41 70 71 22 00 24 00 20 00 0B 05 00 41 80 08 0B 然后直接在控制臺(tái)輸入下邊的代碼: WebAssembly.instantiate(new Uint8Array(` 00 61 73 6D 01 00 00 00 01 17 05 60 00 01 7F 60 00 00 60 01 7F 00 60 01 7F 01 7F 60 02 7F 7F 01 7F 03 07 06 01 04 00 02 03 00 04 05 01 70 01 02 02 05 06 01 01 80 02 80 02 06 0F 02 7F 01 41 90 88 C0 02 0B 7F 00 41 84 08 0B 07 82 01 09 06 6D 65 6D 6F 72 79 02 00 19 5F 5F 69 6E 64 69 72 65 63 74 5F 66 75 6E 63 74 69 6F 6E 5F 74 61 62 6C 65 01 00 03 61 64 64 00 01 0B 5F 69 6E 69 74 69 61 6C 69 7A 65 00 00 10 5F 5F 65 72 72 6E 6F 5F 6C 6F 63 61 74 69 6F 6E 00 05 09 73 74 61 63 6B 53 61 76 65 00 02 0C 73 74 61 63 6B 52 65 73 74 6F 72 65 00 03 0A 73 74 61 63 6B 41 6C 6C 6F 63 00 04 0A 5F 5F 64 61 74 61 5F 65 6E 64 03 01 09 07 01 00 41 01 0B 01 00 0A 30 06 03 00 01 0B 07 00 20 00 20 01 6A 0B 04 00 23 00 0B 06 00 20 00 24 00 0B 10 00 23 00 20 00 6B 41 70 71 22 00 24 00 20 00 0B 05 00 41 80 08 0B`.trim().split(/[\s\r\n]+/g).map(str => parseInt(str, 16)) )).then(({instance}) => { const { add } = instance.exports console.log('2 + 4 =', add(2, 4)) }) 輸出2 + 4 = 6
WebAPI嘗試
我們?cè)賴L試一下流式編譯。直接使用之前的斐波納契數(shù)字的fibonacci.wasm模塊。首先我們需要提供一個(gè)簡單的HTTP服務(wù),用來返回.wasm文件。新建一個(gè)node.js文件。
const http = require('http'); const url = require('url'); const fs = require('fs'); const path = require('path'); const PORT = 8888; // 服務(wù)器監(jiān)聽的端口號(hào); const mime = { "html": "text/html;charset=UTF-8", "wasm": "application/wasm" //當(dāng)遇到對(duì)".wasm"格式文件的請(qǐng)求時(shí),返回特定的MIME頭; }; http.createServer((req, res) => { let realPath = path.join(__dirname, `.${url.parse(req.url).pathname}`); //檢查所訪問文件是否存在,且是否可讀; fs.access(realPath, fs.constants.R_OK, err => { if (err) { res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end(); } else { fs.readFile(realPath, "binary", (err, file) => { if (err) { //文件讀取失敗時(shí)返回500; res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end(); } else { //根據(jù)請(qǐng)求的文件返回相應(yīng)的文件內(nèi)容; let ext = path.extname(realPath); ext = ext ? ext.slice(1) : 'unknown'; let contentType = mime[ext] || "text/plain"; res.writeHead(200, { 'Content-Type': contentType }); res.write(file, "binary"); res.end(); } }); } }); }).listen(PORT); console.log("Server is runing at port: " + PORT + ".");
然后來編寫我們的js部分,講到斐波那契數(shù)字,我們順便做一個(gè)性能的測試,來比較一下使用wasm的方式和原生js的求解速度。
function fibonacciJS(n) { if (n < 2) { return 1; } return fibonacciJS(n - 1) + fibonacciJS(n - 2); } const response = fetch("fibonacci.wasm"); const num = [5, 15, 25, 35, 45]; WebAssembly.instantiateStreaming(response).then( ({ instance }) => { let { fibonacci } = instance.exports; for(let n of num) { console.log(`斐波納切數(shù)字: ${n},運(yùn)行 10 次`) let cTime = 0; let jsTime = 0; for(let time = 0; time < 10; time++) { let start = performance.now(); fibonacci(n) cTime += (performance.now() - start) start = performance.now(); fibonacciJS(n) jsTime += (performance.now() - start) } console.log(`wasm 模塊平均調(diào)用時(shí)間:${cTime / 10}ms`) console.log(`js 模塊平均調(diào)用時(shí)間:${jsTime / 10}ms`) } } )
然后執(zhí)行node node.js開啟http服務(wù),接著在瀏覽器中打開http://localhost:8888/index.html,控制臺(tái)中輸出如下:
斐波納切數(shù)字: 5,運(yùn)行 10 次
index.html:34 wasm 模塊平均調(diào)用時(shí)間:0.001499993959441781ms
index.html:35 js 模塊平均調(diào)用時(shí)間:0.005500001134350896ms
index.html:22 斐波納切數(shù)字: 15,運(yùn)行 10 次
index.html:34 wasm 模塊平均調(diào)用時(shí)間:0.005999993300065398ms
index.html:35 js 模塊平均調(diào)用時(shí)間:0.15650001005269587ms
index.html:22 斐波納切數(shù)字: 25,運(yùn)行 10 次
index.html:34 wasm 模塊平均調(diào)用時(shí)間:0.6239999900572002ms
index.html:35 js 模塊平均調(diào)用時(shí)間:1.1620000121183693ms
index.html:22 斐波納切數(shù)字: 35,運(yùn)行 10 次
index.html:34 wasm 模塊平均調(diào)用時(shí)間:70.59700000681914ms
index.html:35 js 模塊平均調(diào)用時(shí)間:126.21099999523722ms
index.html:22 斐波納切數(shù)字: 45,運(yùn)行 10 次
index.html:34 wasm 模塊平均調(diào)用時(shí)間:8129.7520000021905ms
index.html:35 js 模塊平均調(diào)用時(shí)間:16918.658500007587ms
可以看到wasm很明顯的提高了運(yùn)行速度,運(yùn)行時(shí)間穩(wěn)定在js的一半,當(dāng)規(guī)模達(dá)到45的時(shí)候,wasm的運(yùn)行時(shí)間比js少了整整8秒。
這里也可以看出,如果對(duì)于計(jì)算密集型的應(yīng)用,wasm可以大展身手了,因此WASM適合運(yùn)用于游戲、視頻處理、AR等方面。
不止于WebWasm
除了應(yīng)用在瀏覽器中,也可以應(yīng)用到out-of-web環(huán)境中。通過WASI(WebAssembly System Interface,Wasm操作系統(tǒng)接口)標(biāo)準(zhǔn),Wasm可以直接與操作系統(tǒng)打交道。通過已經(jīng)在各種環(huán)境實(shí)現(xiàn)了WASI標(biāo)準(zhǔn)的虛擬機(jī),我們就可以將wasm用在嵌入式、IOT物聯(lián)網(wǎng)以及甚至云,AI和區(qū)塊鏈等特殊的領(lǐng)域和場景中。
有了WASI標(biāo)準(zhǔn),文章最開始介紹的當(dāng)前應(yīng)用的架構(gòu)在未來可能會(huì)發(fā)生質(zhì)的改變。
上邊架構(gòu)的最大問題就是各個(gè)操作系統(tǒng)不能兼容,同一個(gè)app需要采用不同的語言在不同平臺(tái)下各實(shí)現(xiàn)一次。比如一款A(yù)應(yīng)用,如果想實(shí)現(xiàn)跨平臺(tái)的話,我們需要用java完成在安卓上的開發(fā),用Objective-C實(shí)現(xiàn)iOS上的開發(fā),用C#實(shí)現(xiàn)PC端的開發(fā)...但如果有了wasm,我們只需要選擇任意一門語言,然后編譯成wasm,就可以分發(fā)到各個(gè)平臺(tái)上了。
總結(jié)
目前來講WASM在瀏覽器性能上和JS能打個(gè)55開,互有勝負(fù),WASM有他擅長的地方,JS有JS優(yōu)勢的地方,我認(rèn)為WASM并不會(huì)替代JS/TS,WASM的統(tǒng)一和整合,正如下圖
將所有的開發(fā)語言和所有的運(yùn)行環(huán)境連接起來,只不過因?yàn)閣eb天生的多平臺(tái)性,目前在web端應(yīng)用比較成熟,而這也確實(shí)在某些業(yè)務(wù)上能夠讓前端直接去調(diào)用C++的相關(guān)代碼。
但是就目前來講,你指望一個(gè)前端工程師為了性能優(yōu)化的原因,去學(xué)習(xí)C++然后再編寫C++代碼編譯成.wasm二進(jìn)制文件再放到瀏覽器環(huán)境上運(yùn)行?不可能的,所以目前WASM的應(yīng)用主要還是在JS沒有相應(yīng)合適的三方庫的情況下,去借用C++等后端語言的三方庫,比如視頻處理中的ffmpeg;或者將一部分后端的邏輯處理放到前端,減少服務(wù)壓力,不過這也是很沒必要。
我認(rèn)為,WASM目前階段已經(jīng)具備了很多成熟的應(yīng)用,而且未來可期,作為未來的技術(shù)方向有必要了解一下。
以上就是WebAssembly使用方法研究的詳細(xì)內(nèi)容,更多關(guān)于WebAssembly使用方法的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
前端JavaScript徹底弄懂函數(shù)柯里化curry
隨著主流JavaScript中函數(shù)式編程的迅速發(fā)展, 函數(shù)柯里化在許多應(yīng)用程序中已經(jīng)變得很普遍。 了解它們是什么,它們?nèi)绾喂ぷ饕约叭绾纬浞掷盟鼈兎浅V匾?。本篇文章小編九向大家詳?xì)介紹JavaScript函數(shù)柯里化,需要的小伙伴可以參考下面文字內(nèi)容2021-09-09JS構(gòu)造函數(shù)和實(shí)例化的關(guān)系及原型引入
這篇文章主要介紹了JS構(gòu)造函數(shù)和實(shí)例化的關(guān)系及原型引入,下文圍繞JS構(gòu)造函數(shù)和實(shí)例化的關(guān)系及原型引入的西安海關(guān)資料展開全文內(nèi)容,需要的朋友可以參考一下,希望對(duì)大家有所幫助2021-11-11網(wǎng)站申請(qǐng)不到支付寶接口、微信接口,免接口收款實(shí)現(xiàn)方式幾種解決辦法
這篇文章主要介紹了網(wǎng)站申請(qǐng)不到支付寶接口、微信接口,免接口收款實(shí)現(xiàn)方式幾種解決辦法的相關(guān)資料,需要的朋友可以參考下2016-12-12Dragonfly P2P 傳輸協(xié)議優(yōu)化代碼解析
這篇文章主要為大家介紹了Dragonfly P2P 傳輸協(xié)議優(yōu)化代碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11JavaScript?設(shè)計(jì)模式之洋蔥模型原理及實(shí)踐應(yīng)用
這篇文章主要介紹了JavaScript?設(shè)計(jì)模式之洋蔥模型原理及實(shí)踐應(yīng)用,主要針對(duì)項(xiàng)目中遇到的問題,引申到koa-compose原理解析。通過學(xué)習(xí)洋蔥模式來解決我們實(shí)際項(xiàng)目中的問題2022-09-09微信小程序 實(shí)現(xiàn)tabs選項(xiàng)卡效果實(shí)例代碼
這篇文章主要介紹了微信小程序 實(shí)現(xiàn)tabs選項(xiàng)卡效果實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2016-10-10mitt tiny-emitter發(fā)布訂閱應(yīng)用場景源碼解析
這篇文章主要為大家介紹了mitt tiny-emitter發(fā)布訂閱應(yīng)用場景源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12