
目錄
?? JavaScript語(yǔ)言設(shè)計(jì)缺陷和性能短板
?? 什么是WebAssembly?
?? WebAssembly是如何工作的?
?? 為什么WebAssembly更快?
?? WebAssembly實(shí)戰(zhàn)演練
1. JavaScript語(yǔ)言存在的缺陷和性能短板
1.1 JS 語(yǔ)言設(shè)計(jì)缺陷
-
JavaScript 最初是一種簡(jiǎn)單的腳本語(yǔ)言,旨在為充滿(mǎn)輕量級(jí)超文本文檔的 Web 應(yīng)用帶來(lái)一些交互性。它的設(shè)計(jì)易于學(xué)習(xí)和編寫(xiě),并不追求運(yùn)行速度。多年來(lái),瀏覽器在 JavaScript 解析上的重大性能改進(jìn)的眾多瀏覽器引入了即時(shí)(JIT)編譯使得JavaScript運(yùn)行速度快了一個(gè)量級(jí)。使得JavaScript運(yùn)行速度快了一個(gè)量級(jí)。(大拐點(diǎn))
image.png
但是對(duì)于 JavaScript 這種弱數(shù)據(jù)類(lèi)型的語(yǔ)言來(lái)說(shuō),要實(shí)現(xiàn)一個(gè)完美的 JIT 非常難。因?yàn)镴avascript 是一個(gè)沒(méi)有類(lèi)型的語(yǔ)言,而且像+這樣的符號(hào)又能夠重載,譬如這樣的代碼:
const sum = (a, b, c) => a + b + c;
這是 一個(gè)求和函數(shù),可以直接放在瀏覽器的控制臺(tái)下運(yùn)行,如果傳參都是整數(shù)時(shí),結(jié)果是整數(shù)相加的結(jié)果:如,答案是 6。但是,如果至少有一個(gè)是字符串,則結(jié)果是按照字符串拼接出的結(jié)果,如 console.log(sum('1',2,3)),答案是 "123"。也就是說(shuō),JIT在遇到sum第一個(gè)參數(shù)時(shí)會(huì)編譯成字符串的機(jī)器碼;但是在碰到第二個(gè)sum調(diào)用時(shí),不得不重新編譯一遍。這樣一來(lái),JIT帶來(lái)的效率提升便被抵消了。
1.2 JavaScript性能問(wèn)題
-
我們對(duì)性能的期望是無(wú)盡的,JIT帶來(lái)的性能提升也早已因?yàn)镴avaScript的動(dòng)態(tài)特性達(dá)到了性能天花板。特別在當(dāng)前Web端視頻音頻、圖形圖像處理、VR、AR游戲的日益增多的情況下,問(wèn)題顯得日益突出。無(wú)法滿(mǎn)足一些大型web項(xiàng)目開(kāi)發(fā),于是三大瀏覽器巨頭分別提出了自己的解決方案:
image.png
image.png
我們熟知的四大主流瀏覽器廠商 Google Chrome、Apple Safari、Microsoft Edge 和 Mozilla FireFox ,覺(jué)得Mozilla FireFox所推出的 asm.js 很有前景,為了讓大家都能使用,于是他們就共同參與開(kāi)發(fā),基于asm.js制定一個(gè)標(biāo)準(zhǔn),也就是WebAssembly。
2015年, WebAssembly首次發(fā)布,并可直接在瀏覽器中運(yùn)行
2017 年 3 月份, 四大廠商均宣布已經(jīng)于最新版本的瀏覽器中支持了 WebAssembly 的初始版本,這意味著 - WebAssembly 技術(shù)已經(jīng)實(shí)際落地
2019年,被正式加入Web的標(biāo)準(zhǔn)大家庭中
瀏覽器支持

由圖可見(jiàn),無(wú)論是PC、移動(dòng)端還是服務(wù)器,都已經(jīng)開(kāi)始支持WebAssembly了,這也說(shuō)明WebAssembly已經(jīng)開(kāi)始普及~
2.什么是WebAssembly?
2.1 概念理解
在了解 WebAssembly 之前,讓我們先了解一下機(jī)器語(yǔ)言、匯編語(yǔ)言、高級(jí)語(yǔ)言
計(jì)算機(jī)語(yǔ)言分為三類(lèi)
- 第一代 機(jī)器語(yǔ)言 (相當(dāng)于人類(lèi)的原始階段)
- 第二代 匯編語(yǔ)言 (相當(dāng)于人類(lèi)的手工業(yè)階段)
- 第三代 高級(jí)語(yǔ)言(相當(dāng)于人類(lèi)的工業(yè)階段)
2.1.1 機(jī)器語(yǔ)言
機(jī)器語(yǔ)言(machine code)本質(zhì)上是由“0”和“1”組成的二進(jìn)制數(shù)。計(jì)算機(jī)發(fā)明之初,人們只能計(jì)算機(jī)的語(yǔ)言去命令計(jì)算機(jī)干這干那。向計(jì)算機(jī)每發(fā)出一條指令,就要寫(xiě)出一串串由“0”和“1”組成的指令序列。

因此,使用機(jī)器語(yǔ)言是十分痛苦的,特別是在程序有錯(cuò)需要修改時(shí),更是如此。而且,由于每臺(tái)計(jì)算機(jī)的指令系統(tǒng)往往各不相同,所以,在一臺(tái)計(jì)算機(jī)上執(zhí)行的程序,要想在另一臺(tái)計(jì)算機(jī)上執(zhí)行,必須另編程序,需要進(jìn)行大量重復(fù)繁瑣的工作。
但在當(dāng)時(shí),由于使用的是針對(duì)特定型號(hào)計(jì)算機(jī)的語(yǔ)言,故而運(yùn)算效率是所有語(yǔ)言中最高的。機(jī)器語(yǔ)言,是第一代計(jì)算機(jī)語(yǔ)言。
- 機(jī)器語(yǔ)言的優(yōu)點(diǎn): 直接執(zhí)行,速度快,資源占用少
- 機(jī)器語(yǔ)言的缺點(diǎn):難讀、難編、難記、可移植性差和易出錯(cuò)
2.1.2 匯編語(yǔ)言
為了減輕使用機(jī)器語(yǔ)言編程的痛苦,人們進(jìn)行了一種有益的改進(jìn):用一些簡(jiǎn)潔的英文字母、符號(hào)串來(lái)替代一個(gè)特定的指令的二進(jìn)制串。比如,用“ADD”代表加法。這樣我們很容易讀懂并理解程序在干什么,糾錯(cuò)及維護(hù)都變得方便了,這種程序設(shè)計(jì)語(yǔ)言就稱(chēng)為匯編語(yǔ)言,即第二代計(jì)算機(jī)語(yǔ)言(Assembly)。
Assembly是一種低級(jí)編程語(yǔ)言,使用匯編器(Assembler)可以在一個(gè)進(jìn)程內(nèi)很方便地轉(zhuǎn)換成機(jī)器碼。

2.1.3 高級(jí)語(yǔ)言
不論是機(jī)器語(yǔ)言還是匯編語(yǔ)言都是面向硬件的具體操作的,語(yǔ)言對(duì)機(jī)器的過(guò)分依賴(lài),要求使用者必須對(duì)硬件結(jié)構(gòu)及其工作原理都十分熟悉,這對(duì)非計(jì)算機(jī)專(zhuān)業(yè)人員是難以做到的,對(duì)于計(jì)算機(jī)的推廣應(yīng)用是不利的。人們意識(shí)到,應(yīng)該設(shè)計(jì)一種這樣的語(yǔ)言,這種語(yǔ)言接近于數(shù)學(xué)語(yǔ)言或人的自然語(yǔ)言,同時(shí)又不依賴(lài)于計(jì)算機(jī)硬件,編出的程序能在所有機(jī)器上通用。經(jīng)過(guò)努力,1954年,第一個(gè)完全脫離機(jī)器硬件的高級(jí)語(yǔ)言—FORTRAN問(wèn)世了,40多年來(lái),共有幾百種高級(jí)語(yǔ)言出現(xiàn),有重要意義的有幾十種。其中就包括C++、java等。
高級(jí)語(yǔ)言、匯編語(yǔ)言、機(jī)器語(yǔ)言之間的關(guān)系如下:

所有高級(jí)編程語(yǔ)言都會(huì)被自己的編譯器編譯,比如C++ 被編譯器轉(zhuǎn)換為匯編語(yǔ)言再被匯編器匯編成機(jī)器碼,以便在特定處理器,比如x86\x64\arm上運(yùn)行。不同的處理器架構(gòu)需要不同的機(jī)器代碼和不同的匯編語(yǔ)言(assembly)。

2.2 什么是 webAssembly
WebAssembly(縮寫(xiě)為 Wasm)是一種基于棧式虛擬機(jī)的二進(jìn)制指令格式。Wasm 是一種底層類(lèi)匯編語(yǔ)言,能在 Web 平臺(tái)上以趨近原生應(yīng)用的速度運(yùn)行。C/C++/Rust 等語(yǔ)言將 Wasm 作為編譯目標(biāo)語(yǔ)言,可以將已有的代碼移植到 Web 平臺(tái)中運(yùn)行,以提升代碼復(fù)用度。
咱們能夠從字面上理解,WebAssembly的名字帶個(gè)Assembly(匯編),因此咱們從其名字上就能知道其意思是給Web使用匯編語(yǔ)言,讓W(xué)eb執(zhí)行低級(jí)二進(jìn)制語(yǔ)法。而是如下圖,通過(guò)編譯器把高級(jí)別的語(yǔ)言(C,C++和Rust)編譯為WebAssembly,以便有機(jī)會(huì)在瀏覽器中運(yùn)行。能夠看出來(lái)它實(shí)際上是一種運(yùn)行機(jī)制,一種新的字節(jié)碼格式(.wasm),而不是新的語(yǔ)言。

WebAssembly 的特點(diǎn):
- 層次低,盡量接近機(jī)器語(yǔ)言,這樣解釋器才更容易進(jìn)行 AOT/JIT 編譯,以趨近原生應(yīng)用的速度運(yùn)行 Wasm 程序;
- 作為目標(biāo)代碼,由其他高級(jí)語(yǔ)言編譯器生成;
- 代碼安全可控,不能像真正的匯編語(yǔ)言那樣可以執(zhí)行任意操作;
- 代碼是平臺(tái)無(wú)關(guān)的(不能是平臺(tái)相關(guān)的機(jī)器碼),可以跨平臺(tái)執(zhí)行,采用了虛擬機(jī)/字節(jié)碼技術(shù)。
WebAssembly 目前已經(jīng)在瀏覽器端的圖像處理、音視頻處理、游戲、IDE、可視化、科學(xué)計(jì)算等
3. webAssembly是如何工作的?
工作原理:WebAssembly的工作原理簡(jiǎn)要來(lái)說(shuō)是:我們把C,C++, Rust等靜態(tài)語(yǔ)言的程序編譯成瀏覽器能夠運(yùn)行的wasm二進(jìn)制文件,當(dāng)瀏覽器下載 WebAssembly 代碼時(shí),可以快速將其轉(zhuǎn)換為任何本地機(jī)器碼后運(yùn)行。
設(shè)計(jì)WebAssembly的主要目標(biāo)之一是可移植性。 要在某個(gè)設(shè)備上運(yùn)行應(yīng)用程序,它必須兼容設(shè)備的處理器架構(gòu)和操作系統(tǒng)。這意味著要為支持的操作系統(tǒng)和CPU架構(gòu)的每個(gè)組合編譯源代碼。 使用 WebAssembly ,只需要一次編譯,您的應(yīng)用程序?qū)⒖梢栽诿總€(gè)現(xiàn)代瀏覽器中運(yùn)行。
3.1 LLVM(Low-Level-Virtural-Machine)編譯模型
我們知道大多數(shù)靜態(tài)高級(jí)語(yǔ)言是通過(guò)編譯器前端編譯成為中間代碼,然后再由編譯器后端把中間代碼翻譯成目標(biāo)機(jī)器的可執(zhí)行機(jī)器碼的。

3.2 WebAssembly LLVM
而webAssembly對(duì)應(yīng)的位置則是生成特定平臺(tái)機(jī)器碼之前,類(lèi)似于一種匯編語(yǔ)言,但是它不對(duì)應(yīng)真實(shí)的物理機(jī)器,而是一種瀏覽器抽象成的虛擬處理器,比JavaScript源碼更直接地對(duì)應(yīng)機(jī)器碼。瀏覽器下載wasm文件后只需要做簡(jiǎn)單的編譯生成特定機(jī)器的機(jī)器碼就能執(zhí)行。

如下圖,是WebAssembly的代碼范例 - 它具有易于閱讀的文本格式(.wat),但實(shí)際提供給瀏覽器的內(nèi)容是二進(jìn)制格式(.wasm)。

如下圖:WebAssembly 允許將 C ,C++ 或 Rust 代碼編譯成 WebAssembly 模塊,可以在Web 應(yīng)用中加載并通過(guò)JavaScript調(diào)用。它不是 JavaScript 的替代品,它將與JavaScript一起共存。

4. 為什么WebAssembly更快?
一說(shuō)到WebAssembly,許多文章都會(huì)提及它的快,它的性能優(yōu)勢(shì)都是相對(duì)于JavaScript來(lái)說(shuō)的,但是他為什么快呢?
4.1 體積小
WebAssembly的二進(jìn)制文件比 JavaScript 文本文件小得多。因而下載速度更快,這在網(wǎng)速低的時(shí)候尤為重要。
4.2 ### JIT原理
為了了解為什么WebAssembly有更好的性能,我們首先需要簡(jiǎn)單過(guò)一下瀏覽器JIT的工作原理。
首先JavaScript引擎的工作就是把我們看得懂的編程語(yǔ)言轉(zhuǎn)換成機(jī)器能看懂的語(yǔ)言,我們與機(jī)器的溝通介質(zhì)就是JavaScript引擎,沒(méi)有這個(gè)翻譯官,我們下達(dá)的命令機(jī)器就沒(méi)法理解和執(zhí)行。

編程語(yǔ)言的翻譯有兩種方法:
- 使用解釋器
- 使用編譯器
解釋器是一部分一部分地邊解釋邊執(zhí)行。

編譯器是提前把源代碼整個(gè)編譯成目標(biāo)代碼,直接在支持目標(biāo)代碼的平臺(tái)上運(yùn)行,執(zhí)行過(guò)程不需要編譯器。

兩種方法各有利弊:
解釋器好處除了易于實(shí)現(xiàn)跨平臺(tái)外,最直接的就是對(duì)于我們前端開(kāi)發(fā)人員來(lái)說(shuō),調(diào)試頁(yè)面時(shí),修改一行代碼可以立即看到結(jié)果,不需要等待編譯過(guò)程。但是對(duì)于同樣的代碼需要執(zhí)行多次的情況弊端就很明顯了,它需要執(zhí)行多次的解釋?zhuān)捅热缯f(shuō)執(zhí)行循環(huán)。
編譯器則可以在編譯的時(shí)候可以對(duì)這些重復(fù)的代碼等進(jìn)行優(yōu)化,使得執(zhí)行得更快。由于花了許多時(shí)間在提前優(yōu)化上,所以相對(duì)地需要犧牲的就是編譯代碼的時(shí)間。
在JIT出現(xiàn)以前,JavaScript大多都是從由JavaScript引擎解釋執(zhí)行的,因此效率低下,為了解決性能問(wèn)題,其中一個(gè)辦法就是引入編譯器,將部分代碼優(yōu)化編譯,充分結(jié)合編譯器的優(yōu)勢(shì),結(jié)合解釋器與即時(shí)編譯器(JIT)以提升性能早已在python等語(yǔ)言上得到很好的實(shí)踐證實(shí),所以瀏覽器廠商們就紛紛引入了JIT。
JIT的實(shí)現(xiàn)方法就是在JavaScript引擎中實(shí)現(xiàn)一個(gè)監(jiān)視器,這個(gè)監(jiān)視器監(jiān)視運(yùn)行的代碼,記錄下代碼各自的運(yùn)行次數(shù)并標(biāo)記它們的熱點(diǎn)類(lèi)型。如果發(fā)現(xiàn)某一段代碼執(zhí)行了較多次,將標(biāo)記為’warm’,如果執(zhí)行了許多次,那么就會(huì)被標(biāo)記為’hot’

JIT會(huì)把標(biāo)記為warm的代碼送到基線編譯器(Baseline compiler)中編譯,并且存儲(chǔ)編譯結(jié)果,當(dāng)解釋器繼續(xù)解釋時(shí),監(jiān)視器發(fā)現(xiàn)了同樣的代碼,那么就會(huì)把剛才編譯好的結(jié)果推給瀏覽器,讓瀏覽器使用較快的編譯版本。

在基線編譯器中,代碼會(huì)進(jìn)行一定程度的優(yōu)化,但是由于優(yōu)化需要時(shí)間,代碼只是warm狀態(tài),所以基線編譯器并不會(huì)花太長(zhǎng)時(shí)間去優(yōu)化。
不過(guò)如果標(biāo)記為warm的代碼執(zhí)行了更多次呢? 代碼已經(jīng)非常的hot了,這時(shí)花更多時(shí)間去優(yōu)化它就非常有必要了,所以監(jiān)視器會(huì)將這段代碼放到優(yōu)化編譯器(Optimizing compiler)中,生成更加快速高效的代碼,這時(shí)如果監(jiān)視器發(fā)現(xiàn)了同樣的代碼,就會(huì)返回這個(gè)更加快的優(yōu)化編譯版本。

然而,由于Javascript是弱類(lèi)型的語(yǔ)言,它的靈活性可能會(huì)導(dǎo)致在幾百個(gè)循環(huán)后某一次循環(huán)中這個(gè)對(duì)象少了某個(gè)屬性,那么JIT檢測(cè)到后會(huì)認(rèn)為這個(gè)優(yōu)化的編譯代碼不合理,會(huì)將這個(gè)編譯代碼丟棄掉,轉(zhuǎn)而使用基線編譯版本或者也可能直接回到解釋器。
這個(gè)過(guò)程叫做去優(yōu)化(JIT監(jiān)視到如果某段代碼進(jìn)行了幾次優(yōu)化到去優(yōu)化的循環(huán)后,會(huì)終止這段代碼的優(yōu)化編譯,防止無(wú)限的循環(huán),盡管如此,這里的性能損耗仍不可忽視)

舉個(gè)簡(jiǎn)單的例子,對(duì)于以下這段循環(huán)代碼,在基線編譯器階段,每一行代碼會(huì)被基線編譯器編譯成代碼樁
function arraySum (arr) {
let sum = 0
for (let i = 0; i < arr.length; i++) {
sum += arr[i]
}
}
但是對(duì)于累加sum += arr[i]這一句代碼,我們并沒(méi)有確定sum和arr是什么類(lèi)型,如果是數(shù)字,基線編譯器會(huì)生成一個(gè)樁(stub),如果是字符串,它也會(huì)為它生成一個(gè)樁,也就是說(shuō)在調(diào)用過(guò)程中,可能有一個(gè)以上的樁,這時(shí)瀏覽器再次執(zhí)行這段代碼時(shí),需要進(jìn)行多次分支選擇,進(jìn)行類(lèi)型檢查。

所以,如果類(lèi)型不固定,使用的是基線編譯版本,每次循環(huán)都要進(jìn)行一次類(lèi)型檢查。

如果類(lèi)型固定,使用的是優(yōu)化編譯器后的版本,在循環(huán)之前就進(jìn)行一次類(lèi)型檢查就可以了,速度提升是相當(dāng)顯著的。

因此,使用JavaScript在靈活與興能上存在一定的折中關(guān)系,享受靈活的同時(shí)必然有一定的性能損耗。
總的來(lái)說(shuō)通過(guò)JIT編譯器,JavaScript的性能有了很大的提升,通過(guò)使用基線編譯版本或者優(yōu)化編譯版本,能夠大大減少解釋器的時(shí)間損耗。但是JIT也有一定的瓶頸,主要體現(xiàn)在:
JIT 編譯器花了很多時(shí)間在猜測(cè) Javascript 中的類(lèi)型。
- 在優(yōu)化和去優(yōu)化過(guò)程中造成了很大開(kāi)銷(xiāo)
- 而使用WebAssembly的出現(xiàn)的原因之一,就是為了消除這些開(kāi)銷(xiāo)。
3.3 JIT與WebAssembly的時(shí)間耗時(shí)對(duì)比

我們來(lái)看看運(yùn)行一段JavaScript代碼時(shí),JavaScript引擎所花的時(shí)間的大概分布
- parse : 解釋。對(duì)JavaScript源碼進(jìn)行解釋?zhuān)沙橄笳Z(yǔ)法樹(shù)或者字節(jié)碼,傳遞給解釋器。
- compile + optimize : 編譯+優(yōu)化。解釋器生成字節(jié)碼,并通過(guò)編譯器(JIT)編譯優(yōu)化部分字節(jié)碼,生成機(jī)器碼。
- re-optimize : 發(fā)生去優(yōu)化時(shí),重新優(yōu)化所花的時(shí)間。
- execute : 執(zhí)行代碼的過(guò)程。
- garbage collection: 清理內(nèi)存的時(shí)間。
需要注意的是,這幾個(gè)部分的工作在線程中是交替進(jìn)行的,一段代碼中某一部分可能在解釋、然后某一部分可能在去優(yōu)化、然后某一部分可能在執(zhí)行。這里的圖的順序只是為了方便描述。
而需要提及的是,過(guò)去沒(méi)有JIT時(shí),JavaScript的執(zhí)行時(shí)間需要更多的時(shí)間,就如同下圖所示:

那么對(duì)于執(zhí)行一段相同功能的WebAssembly代碼的時(shí)間分布大概是怎樣的呢?讓我們逐步比對(duì)一下。
WebAssembly本身就以二進(jìn)制形式提供,解析速度更快。它是靜態(tài)類(lèi)型的,因此與JavaScript不同,引擎在編譯期間不需要類(lèi)型推斷。大多數(shù)優(yōu)化都是在編譯源代碼期間,在瀏覽器執(zhí)行之前進(jìn)行的。內(nèi)存是手動(dòng)管理的,就像 C 和 C++ 這樣的語(yǔ)言一樣,所以也沒(méi)有垃圾收集。所有這些都是為了提供了更好,更可靠的性能。
正是因?yàn)閃asm的大部分優(yōu)化工作已經(jīng)在LLVM的前端部分完成了,所以編譯優(yōu)化的工作很少,這便是其高性能的主要體現(xiàn)。
WebAssembly代碼在瀏覽器中的執(zhí)行過(guò)程:
- 解碼
- 編譯
- 執(zhí)行

WebAssembly就只有解碼、編譯優(yōu)化和執(zhí)行這三部分開(kāi)銷(xiāo),對(duì)比原生性能開(kāi)銷(xiāo)減少了許多

5. WebAssembly實(shí)戰(zhàn)演練
WebAssembly是一個(gè)具有WASM擴(kuò)展名的文件,可以把它看作一個(gè)可以導(dǎo)入JavaScript程序的模塊。那么如何生成WASM文件呢?
5.1 emscripten 安裝與使用, 讓C語(yǔ)言出現(xiàn)在前端
官方推薦方式,先下載 emsdk:
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
# 下載并安裝最新的 SDK 工具.
./emsdk install latest
# 為當(dāng)前用戶(hù)激活最新的 SDK. (寫(xiě)入 .emscripten 配置文件)
./emsdk activate latest
# 激活當(dāng)前 PATH 環(huán)境變量
source ./emsdk_env.sh
驗(yàn)證
emcc -v 不報(bào)錯(cuò)就成功了
編譯
接下來(lái)就可以編譯代碼啦。
來(lái)個(gè)萬(wàn)年不變的Hello world試試:
- 在emsdk文件夾下創(chuàng)建test.c文件
#include<stdio.h>
void main(){
printf("Hello world!");
}
- 使用剛才已經(jīng)配置過(guò)的終端,找到test.c文件,執(zhí)行以下命令
emcc ./test.c -s WASM=1 -o ./test.html
emcc 是Emscripten編譯器行命令
test.c 是咱們的輸入文件
-s WASM=1 指定咱們想要的wasm輸出形式。若是咱們不指定這個(gè)選項(xiàng),Emscripten默認(rèn)將只會(huì)生成asm.js。(可參考 emcc --help 參數(shù)說(shuō)明)
-o ./test.html 指定這個(gè)選項(xiàng)將會(huì)生成HTML頁(yè)面來(lái)運(yùn)行咱們的代碼,而且會(huì)生成wasm模塊,以及編譯和實(shí)例化wasm模塊所須要的“膠水”js代碼,這樣咱們就能夠直接在web環(huán)境中使用了。
執(zhí)行后會(huì)產(chǎn)生三個(gè)新文件:test.wasm 二進(jìn)制的wasm模塊代碼,雖然本地打不開(kāi),可是瀏覽器能夠幫忙翻譯。
test.js 一個(gè)包含了用來(lái)在原生C函數(shù)和JavaScript/wasm之間轉(zhuǎn)換的膠水代碼的JavaScript文件
test.html 一個(gè)用來(lái)加載,編譯,實(shí)例化你的wasm代碼而且將它輸出在瀏覽器顯示上的一個(gè)HTML文件
啟動(dòng)http服務(wù)命令,查看運(yùn)行結(jié)果
emrun --no_browser --port 8080 ./test.html
5.2 webAssembly 參與頁(yè)面中大計(jì)算量功能demo
- 修改test.c中的C語(yǔ)言代碼為斐波那契數(shù)列:
#include <stdio.h>
int fib(int n)
{
if (n <= 1)
return n;
return fib(n-1) + fib(n-2);
}
- 編譯生成wasm文件
emcc ./test.c -Os -s WASM=1 -s SIDE_MODULE=1 -o ./test.wasm
- emcc就是Emscripten編譯器,
- test.c是咱們的輸入文件
- -Os表示此次編譯須要優(yōu)化(能夠指定優(yōu)化策略。emcc --help)
- -s 后緊跟編譯的配置(setting)WASM=1表示輸出wasm的文件,由于默認(rèn)的是輸出asm.js
- -s 后緊跟編譯的配置(setting)SIDE_MODULE=1表示就只要這一個(gè)模塊,不要給我其余亂七八糟的代碼
- -o 表示輸出的文件,test.wasm是咱們的輸出文件。
所有配置項(xiàng)可以在這里查看。
- 書(shū)寫(xiě)頁(yè)面demo
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<title>Page Title</title>
<meta name='viewport' content='width=device-width, initial-scale=1'>
</head>
<body>
<script>
// 斐波那契數(shù)列
let fib;
function fibjs(n) {
if (n <= 1)
return n;
return fibjs(n - 1) + fibjs(n - 2);
}
function loadWebAssembly(path, imports = {}) {
return fetch(path) // 加載文件
.then(response => response.arrayBuffer()) // 轉(zhuǎn)成 ArrayBuffer
.then(buffer => WebAssembly.compile(buffer))
.then(module => {
return new WebAssembly.Instance(module);
})
}
loadWebAssembly('./test.wasm')
.then(instance => {
fib = instance.exports.fib;
});
function perforweb(n){
console.log('webAssembly demo 開(kāi)始');
let startTime = performance.now();
let c = fib(n);
let endTime = performance.now();
console.log(`webAssembly耗時(shí) ${endTime - startTime} 毫秒,最終結(jié)果為 ${c}`);
return true;
}
function perforjs(n){
console.log('javascript demo 開(kāi)始');
let startTime = performance.now();
let j= fibjs(n);
let endTime = performance.now();
console.log(`javascript耗時(shí) ${endTime - startTime} 毫秒, 最終結(jié)果為 ${j}`);
}
</script>
</body>
</html>
- 安裝live-server,本地起服務(wù)
npm install -g live-server
-
瀏覽器控制臺(tái)中分別執(zhí)行2個(gè)函數(shù)
image.png
可以看到執(zhí)行時(shí)間的對(duì)比,webAssembly在計(jì)算大數(shù)據(jù)量,存在大量遞歸,遍歷的操作時(shí),性能提升非常明顯。




