摘要:本文是圖說系列文章的第四篇。它們表示一種可以在普遍流行機器上高效使用的指令集合。這是因為是一種稱為堆棧機器。盡管是根據堆棧機器來設計的,但是這并不是它在真實物理機器上工作的方式。這些內容稱為段。
本文是圖說 WebAssembly 系列文章的第四篇。如果您還未閱讀之前的文章,建議您從第一篇入手。
WebAssembly 是一種使得除 JavaScript 以外的編程語言也能運行在網頁上的技術。
在過去,當我們需要通過編程來控制網頁內容時,我們的選擇只有 JavaScript 。
所以當大家都說 WebAssembly 運行速度很快時,其實它的比較對象就是指 JavaScript 。
不過這并不意味著你只能使用 JavaScript 和 WebAssembly 中的一種。
反而,更推薦的做法是同時使用它們。即便是你不寫 WebAssembly ,你也是可以從它身上獲得好處的。
WebAssembly 模塊定義了可以被 JavaScript 調用的函數。
就像我們現在可以直接從 npm 下載 lodash 模塊并調用其接口一樣,未來我們也可以下載 WebAssembly 模塊并使用它。
所以,今天我們來看看如何創建 WebAssembly 模塊,以及如何使用 JavaScript 調用它。
角色在上一篇文章中,我們介紹了編譯器如何把高級語言編譯為機器碼。
在上圖中,WebAssembly 對應哪個角色呢?
聰明的你可能已經想到,它只不過是另一種目標匯編語言而已。
從某種意義上來說,這種想法是對的,只不過圖中的 x86、ARM 等其實對應的是一種特定的計算機架構。
對于開發者來說,他所開發的代碼是希望能夠運行在互聯網上所有用戶機器上的,但是他其實并不知道運行這些代碼的機器屬于哪種架構。
所以 WebAssembly 跟匯編相比還是有略微不同之處。
它面向的是一種概念上機器的機器語言,而不是一種真實存在的物理機器。
這也就導致了 WebAssembly 指令是一種虛擬指令。
與 JavaScript 源碼相比,虛擬指令跟機器碼的映射來得更為直接。
它們表示一種可以在普遍流行機器上高效使用的指令集合。但同時它們也不會直接映射到特定的機器碼。
瀏覽器會下載 WebAssembly,然后把它變成目標機器的匯編。
編譯目前對 WebAssembly 支持最多的編譯器工具鏈稱為 LLVM 。有很多不同的編譯器前端和后端都在使用 LLVM 。
注意: 大多數的 WebAssembly 模塊開發者都會使用 C 和 Rust 這樣的語言,然后編譯為 WebAssembly,但是也有其他方式創建 WebAssembly 模塊。比如,有一個實驗工具可以把 TypeScript 編譯為 WebAssembly 模塊,更有甚者,
可以直接手寫 WebAssembly 。
這里,假如我們想把 C 編譯為 WebAssembly 。
我們可以使用 C 語言編譯器前端把 C 代碼編譯為 LLVM 中間代碼。一旦變成 LLVM 的中間代碼,LLVM 就可以理解并分析代碼,然后做一些優化。
為了把 LLVM 中間代碼變成 WebAssembly,我們還需要一個編譯器后端。剛好,LLVM 項目中確實有一個正在開發編譯器后端,未來它應該是大部分人的共同選擇,而且應該很快就要完成了。不過,現在用它的話還是相當棘手。
不過不用灰心,還有另一個工具稱為 Emscripten,目前用起來會更加簡單點。
它擁有自己編譯器后端,可以把中間代碼編譯為 asm.js ,進而轉化為 WebAssembly 。
不過它也支持 LLVM,因此我們也可以在 Emscripten 和其他后端之間相互切換。
Emscripten 還包含了很多其他工具和庫,允許開發者移植整個 C/C++ 代碼,因此與其說它是編譯器,其實它更像是軟件開發套件(SDK)。
不管用什么工具鏈,最終的結果都是得到一個 .wasm 文件。后面我們會介紹 .wasm 文件的結構,不過首先讓我們來看看如何在 JavaScript 中使用它。
加載.wasm 文件就是 WebAssembly 模塊,它可以直接使用 JavaScript 加載。
截止到目前,這種加載方式略微復雜。
function fetchAndInstantiate(url, importObject) { return fetch(url).then(res => res.arrayBuffer()) .then(bytes => WebAssembly.instantiate(bytes, importObject)) .then(results => results.instance); }
想深入的話,可以參考這個MDN 文檔
我們正在努力把這個過程變得更加簡單。我們也希望能夠把工具鏈變得更加友好,希望能夠直接集成到諸如 webpack 或者 SystemJS 等打包器中。相信未來 WebAssembly 模塊可以跟加載 JavaScript 模塊一樣簡單好用。
不過,WebAssembly 模塊和 JavaScript 模塊之間有一個主要的不同之處。
當前,WebAssembly 模塊中的函數只能使用數字作為參數或者返回值。
對于其他任何更復雜的數據類型,如字符串,我們必須直接操作 WebAssembly 模塊的內存。
如果你大部分的時間都在使用 JavaScript,那么你可能對直接操作內容不太熟悉。
像 C、C++ 和 Rust 這些高性能的語言,它們都必須手動管理內存。
WebAssembly 模塊的內存就模擬了這些語言的堆內存。
為了能夠操作內存,我們需要使用 JavaScript 中的 ArrayBuffer。
它是字節數組,所以它的索引當做內存地址來使用。
如果想要在 JavaScript 和 WebAssembly 之間傳遞字符串,那么必須先把字符串轉為等效的字符碼,然后寫入 ArrayBuffer。由于數組索引是整數,所以索引可以傳遞給 WebAssembly 函數。這樣,索引就變成了指向字符串首個字符的指針了。
不過大部分情況下,WebAssembly 模塊開發者都會把模塊做友好地封裝。此時,模塊的使用者可能就沒必要知道其內部是如何管理內存的了。
如果你對內存管理感興趣,可以查看 MDN 文檔結構
如果你編程使用的是高級語言然后編譯為 WebAssembly,那其實你沒必要了解 WebAssembly 模塊的結構,不過它可以幫你理解基礎信息。
下面是一個 C 函數,我們將把它編譯為 WebAssembly 。
int add42(int num) { return num + 42; }
你可以使用 WasmExplorer來編譯這個函數。
打開編譯好的 .wasm 文件后,我們可能會看到類似以下的內容:
00 61 73 6D 0D 00 00 00 01 86 80 80 80 00 01 60 01 7F 01 7F 03 82 80 80 80 00 01 00 04 84 80 80 80 00 01 70 00 00 05 83 80 80 80 00 01 00 01 06 81 80 80 80 00 00 07 96 80 80 80 00 02 06 6D 65 6D 6F 72 79 02 00 09 5F 5A 35 61 64 64 34 32 69 00 00 0A 8D 80 80 80 00 01 87 80 80 80 00 00 20 00 41 2A 6A 0B
這是模塊的“二進制”表示。之所以給“二進制”加上引號,是因為二進制表示通常是顯示為十六進制的,但是可以很簡單的轉為二進制,或者人類可讀的格式。
舉例來說,下面是 num + 42 的模樣:
運行你可能會對上圖中的內容感到疑惑,下面我們把這些指令的作用標注出來。
你可能已經注意到 add 操作并沒有說要相加的兩個數從哪里來。這是因為 WebAssembly 是一種稱為堆棧機器。這意味著,操作碼在操作之前,它所需的操作數已經在堆棧的隊列當中了。
像 add 這樣的操作碼本身就知道它需要多少個操作數。因為 add 需要兩個操作數,所以它會從堆棧的頂部取出兩個值來作為操作數。
這樣種設計中,add 指令可以變得很短,只占用一字節,因為它并不需要指定源和目標寄存器地址。這樣就減小了 .wasm 文件的大小,從而更利于網絡傳輸。
盡管 WebAssembly 是根據堆棧機器來設計的,但是這并不是它在真實物理機器上工作的方式。
當瀏覽器把 WebAssembly 編譯為機器碼時,它仍然會用到寄存器。不過,由于 WebAssembly 代碼并不指定寄存器,所以瀏覽器能夠更自由的為其指定最高效的寄存器。
除了 add42 函數本身,.wasm 也還包含了其他內容。這些內容稱為段(Section)。有些段是任何模塊都必須有的,有些則是可選的。
必選的有:
類型:包含模塊中函數和任何導入函數的函數簽名。
函數:給模塊中的每個函數提供索引。
代碼:模塊中每個函數的函數體。
可選的有:
導出:使得函數、內存、表格和全局變量對其他模塊和 JS 可訪問。這可以使得模塊可以多帶帶編譯,然后動態鏈接起來。
導入:指定從其他模塊或者 JS 中導入的函數、內存、表格和全局變量等。
入口:模塊加載時自動運行的函數。
全局:模塊中的全局變量聲明。
內存:定義模塊使用的內存。
表格:用于映射不透明值,這些值不能在 WebAssembly 中表示或直接訪問,例如 JS 的對象。
數據:用于初始化導入的或本地的內存
元素:用于初始化導入的或者本地的表格
更多的資料可參考 MDN 文檔結束
經過本文,相信你已經知道該如何使用 WebAssembly 模塊了。下一篇文章我們將探索它為何如此快。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/94766.html
摘要:性能簡史在年,被創造出來時并不是沖著性能去的。而且在之后的十年發展中,它的性能一直是很低的。的引入成就了性能提升的一個轉折點,其執行速度比以往快了之多。性能提升也使得在全新的問題上使用成為可能。現在,極可能是下一個性能轉折點。 你可能已經聽說 WebAssembly 代碼跑起來非常快。但是你知道這是為什么嗎?在本系列文章中,我們將探究其原因。 何為 WebAssembly WebAss...
摘要:現狀年月日,主流的四大瀏覽器達成了共識并宣布的最小可行產品已經完成。更快的函數調用當前,在中調用函數比想象的要慢。直接操作目前,沒有任何方式能夠操作。這就導致了部分應用可能會因此而推遲發布時間。結束現如今已經相當快速。 本文是圖說 WebAssembly 系列文章的最后一篇。如果您還未閱讀之前的文章,建議您從第一篇入手。 現狀 2017 年 2 月 28 日,主流的四大瀏覽器達成了共識...
摘要:本文是圖說系列文章的第五篇。這樣的話,使用的開發者也不需要做任何適配,但是它們卻能獲得更高性能。該圖并不是用來準確的衡量其性能的。運行編寫出高性能的代碼是可能的。這種清理工作由引擎自動進行,稱為垃圾回收。 本文是圖說 WebAssembly 系列文章的第五篇。如果您還未閱讀之前的文章,建議您從第一篇入手。 在上一篇文章中,我們說到了使用 WebAssembly 和 JavaScript...
摘要:編譯器優缺點與解釋器相比,編譯器有著相反的優缺點。它們為引擎新增了一個組件,稱為監視器,或者。優化編譯器會基于監視器記錄的代碼運行信息來作出一些判斷。通常來說,優化編譯器會使得代碼跑的更快。而這正是優化編譯器所做的優化之一。 本文是圖說 WebAssembly 系列文章的第二篇,如果你還沒閱讀其它的,建議您從第一篇開始。 JavaScript 的運行,一開始是很慢的,但是后面會變得越來...
摘要:為了更好的理解,我們有必要去先理解什么是匯編,以及編譯器是如何產生匯編的。什么是匯編現在,我們來看看外星人的大腦是如何工作的。這些注釋就是匯編,也稱為符號機器碼。結束以上的內容就是什么是匯編以及它是如何從高級編程語言翻譯過來的。 本文是圖說 WebAssembly 系列文章的第三篇。如果您還未閱讀之前的文章,建議您從第一篇入手。 為了更好的理解 WebAssembly ,我們有必要去先...
閱讀 6912·2021-09-22 15:08
閱讀 1920·2021-08-24 10:03
閱讀 2437·2021-08-20 09:36
閱讀 1315·2020-12-03 17:22
閱讀 2474·2019-08-30 15:55
閱讀 905·2019-08-29 16:13
閱讀 3053·2019-08-29 12:41
閱讀 3249·2019-08-26 12:12