成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

當前 WebAssembly 的狀況

開發 開發工具
這篇文章介紹WebAssembly 工作原理以及為什么 WebAssembly 運行的更快。

上一篇文章《關于WebAssembly 的背景知識》讓大家基本了解了 WebAssembly ,接下來我們繼續介紹WebAssembly 工作原理以及為什么 WebAssembly 運行的更快。

一、WebAssembly 工作原理

WebAssembly 是除了 JavaScript 以外,另一種可以在網頁中運行的編程語言。過去如果你想在瀏覽器中運行代碼來對網頁中各種元素進行控制,只有 JavaScript 這一種選擇。

所以當人們談論 WebAssembly 的時候,往往會拿 JavaScript 來進行比較。但是它們其實并不是“二選一”的關系——并不是只能用 WebAssembly 或者 JavaScript。

實際上,我們鼓勵開發者將這兩種語言一起使用,即使你不親自實現 WebAssembly 模塊,你也可以學習它現有的模塊,并它的優勢來實現你的功能。

WebAssembly 模塊定義的一些功能可以通過 JavaScript 來調用。所以就像你通過 npm 下載 lodash 模塊并通過 API 使用它一樣,未來你也可以下載 WebAssembly 模塊并且使用其提供的功能。

那么,就讓我們來看一下如何開發 WebAssembly 模塊,以及如何通過 JavaScript 使用他們。

1. WebAssembly 處于哪個環節?

上一篇關于WebAssembly 背景知識的文章中,我介紹了編譯器是如何從高級語言翻譯到機器碼的。

那么在上圖中,WebAssembly 在什么位置呢?實際上,你可以把它看成另一種“目標匯編語言”。

每一種目標匯編語言(x86、ARM)都依賴于特定的機器結構。當你想要把你的代碼放到用戶的機器上執行的時候,你并不知道目標機器結構是什么樣的。

而 WebAssembly 與其他的匯編語言不一樣,它不依賴于具體的物理機器。可以抽象地理解成它是概念機器的機器語言,而不是實際的物理機器的機器語言。

正因為如此,WebAssembly 指令有時也被稱為虛擬指令。它比 JavaScript 代碼更直接地映射到機器碼,它也代表了“如何能在通用的硬件上更有效地執行代碼”的一種理念。所以它并不直接映射成特定硬件的機器碼。

WebAssembly 指令有時也被稱為虛擬指令

瀏覽器把 WebAssembly 下載下來后,可以迅速地將其轉換成機器匯編代碼。

2. 編譯到 .wasm 文件

目前對于 WebAssembly 支持情況***的編譯器工具鏈是 LLVM。有很多不同的前端和后端插件可以用在 LLVM 上。

提示:很多 WebAssembly 開發者用 C 語言或者 Rust 開發,再編譯成 WebAssembly。其實還有其他的方式來開發 WebAssembly 模塊。例如利用 TypeScript 開發 WebAssembly 模塊,或者直接用文本格式的 WebAssembly 也可以。

假設想從 C 語言到 WebAssembly,我們就需要 clang 前端來把 C 代碼變成 LLVM 中間代碼。當變換成了 LLVM IR 時,說明 LLVM 已經理解了代碼,它會對代碼自動地做一些優化。

為了從 LLVM IR 生成 WebAssembly,還需要后端編譯器。在 LLVM 的工程中有正在開發中的后端,而且應該很快就開發完成了,現在這個時間節點,暫時還看不到它是如何起作用的。

還有一個易用的工具,叫做 Emscripten。它通過自己的后端先把代碼轉換成自己的中間代碼(叫做 asm.js),然后再轉化成 WebAssembly。實際上它背后也是使用的 LLVM。

Emscripten 還包含了許多額外的工具和庫來包容整個 C/C++ 代碼庫,所以它更像是一個軟件開發者工具包(SDK)而不是編譯器。例如系統開發者需要文件系統以對文件進行讀寫,Emscripten 就有一個 IndexedDB 來模擬文件系統。

不考慮太多的這些工具鏈,只要知道最終生成了 .wasm 文件就可以了。后面我會介紹 .wasm 文件的結構,在這之前先一起了解一下在 JS 中如何使用它。

3. 加載一個 .wasm 模塊到 JavaScript

.wasm 文件是 WebAssembly 模塊,它可以加載到 JavaScript 中使用,現階段加載的過程稍微有點復雜。

  1. function fetchAndInstantiate(url, importObject) { 
  2.   return fetch(url).then(response => 
  3.     response.arrayBuffer() 
  4.   ).then(bytes => 
  5.     WebAssembly.instantiate(bytes, importObject) 
  6.   ).then(results => 
  7.     results.instance 
  8.   ); 

如果想深入了解,可以在 MDN 文檔中了解更多。

我們一直在致力于把這一過程變得簡單,對工具鏈進行優化。希望能夠把它整合到現有的模塊打包工具中,比如 webpack 中,或者整合到加載器中,比如 SystemJS 中。我們相信加載 WebAssembly 模塊也可以像加載 JavaScript 一樣簡單。

這里介紹 WebAssembly 模塊和 JavaScript 模塊的主要區別。當前的 WebAssembly 只能使用數字(整型或者浮點型)作為參數或者返回值。

對于任何其他的復雜類型,比如 string,就必須得用 WebAssembly 模塊的內存操作了。如果是經常使用 JavaScript,對直接操作內存不是很熟悉的話,可以回想一下 C、C++ 和 Rust 這些語言,它們都是手動操作內存。WebAssembly 的內存操作和這些語言的內存操作很像。

為了實現這個功能,它使用了 JavaScript 中稱為 ArrayBuffer 的數據結構。ArrayBuffer 是一個字節數組,所以它的索引(index)就相當于內存地址了。

如果你想在 JavaScript 和 WebAssembly 之間傳遞字符串,可以利用 ArrayBuffer 將其寫入內存中,這時候 ArrayBuffer 的索引就是整型了,可以把它傳遞給 WebAssembly 函數。此時,***個字符的索引就可以當做指針來使用。

這就好像一個 web 開發者在開發 WebAssembly 模塊時,把這個模塊包裝了一層外衣。這樣其他使用者在使用這個模塊的時候,就不用關心內存管理的細節。

如果你想了解更多的內存管理,看一下我們寫的 WebAssembly 的內存操作。

4. .wasm 文件結構

如果你是寫高級語言的開發者,并且通過編譯器編譯成 WebAssembly,那你不用關心 WebAssembly 模塊的結構。但是了解它的結構有助于你理解一些基本問題。

如果你對編譯器還不了解,建議先讀一下“系列三之編譯器如何生成匯編這篇文章。

這段代碼是即將生成 WebAssembly 的 C 代碼:

  1. int add42(int num) { 
  2.     return num + 42; 

你可以使用 WASM Explorer 來編譯這個函數。

打開 .wasm 文件(假設你的編輯器支持的話),可以看到下面代碼:

  1. 00 61 73 6D 0D 00 00 00 01 86 80 80 80 00 01 60 
  2. 01 7F 01 7F 03 82 80 80 80 00 01 00 04 84 80 80 
  3. 80 00 01 70 00 00 05 83 80 80 80 00 01 00 01 06 
  4. 81 80 80 80 00 00 07 96 80 80 80 00 02 06 6D 65 
  5. 6D 6F 72 79 02 00 09 5F *** 35 61 64 64 34 32 69 
  6. 00 00 0A 8D 80 80 80 00 01 87 80 80 80 00 00 20 
  7. 00 41 2A 6A 0B 

這是模塊的“二進制”表示。之所以用引號把“二進制”引起來,是因為上面其實是用十六進制表示的,不過把它變成二進制或者人們能看懂的十進制表示也很容易。

例如,下面是 num + 42 的各種表示方法。

5. 代碼是如何工作的:基于棧的虛擬機

如果你對具體的操作過程很好奇,那么這幅圖可以告訴你指令都做了什么。

從圖中我們可以注意到 加 操作并沒有指定哪兩個數字進行加。這是因為 WebAssembly 是采用“基于棧的虛擬機”的機制。即一個操作符所需要的所有值,在操作進行之前都已經存放在堆棧中。

所有的操作符,比如加法,都知道自己需要多少個值。加需要兩個值,所以它從堆棧頂部取兩個值就可以了。那么加指令就可以變的更短(單字節),因為指令不需要指定源寄存器和目的寄存器。這也使得 .wasm 文件變得更小,進而使得加載 .wasm 文件更快。

盡管 WebAssembly 使用基于棧的虛擬機,但是并不是說在實際的物理機器上它就是這么生效的。當瀏覽器翻譯 WebAssembly 到機器碼時,瀏覽器會使用寄存器,而 WebAssembly 代碼并不指定用哪些寄存器,這樣做的好處是給瀏覽器***的自由度,讓其自己來進行寄存器的***分配。

6. WebAssembly 模塊的組成部分

除了上面介紹的,.wasm 文件還有其他部分。一些組成部分對于模塊來講是必須的,一些是可選的。

必須部分:

  • Type。在模塊中定義的函數的函數聲明和所有引入函數的函數聲明。
  • Function。給出模塊中每個函數一個索引。
  • Code。模塊中每個函數的實際函數體。

可選部分:

  • Export。使函數、內存、表(tables)、全局變量等對其他 WebAssembly 或 JavaScript 可見,允許動態鏈接一些分開編譯的組件,即 .dll 的WebAssembly 版本。
  • Import。允許從其他 WebAssembly 或者 JavaScript 中導入指定的函數、內存、表或者全局變量。
  • Start。當 WebAssembly 模塊加載進來的時候,可以自動運行的函數(類似于 main 函數)。
  • Global。聲明模塊的全局變量。
  • Memory。定義模塊用到的內存。
  • Table。使得可以映射到 WebAssembly 模塊以外的值,如映射到 JavaScript 的對象。這在間接函數調用時很有用。
  • Data。初始化導入的或者局部內存。
  • Element。初始化導入的或者局部的表。

如果你想了解關于這些組成部分的更深入的內容,可以閱讀這些組成部分的工作原理。

二、為什么 WebAssembly 更快?

上面我介紹了如何編寫 WebAssembly 程序,也表達了我希望看到更多的開發者在自己的工程中同時使用 WebAssembly 和 JavaScript 的期許。

開發者們不必糾結于到底選擇 WebAssembly 還是 JavaScript,已經有了 JavaScript 工程的開發者們,希望能把部分 JavaScript 替換成 WebAssembly 來嘗試使用。

例如,正在開發 React 程序的團隊可以把協調性代碼(即虛擬 DOM)替換成 WebAssembly 的版本。而對于你的 web 應用的用戶來說,他們就跟以前一樣使用,不會發生任何變化,同時他們還能享受到 WebAssembly 所帶來的好處——快。

而開發者們選擇替換為 WebAssembly 的原因正是因為 WebAssembly 比較快。

1. 當前的 JavaScript 性能如何?

在我們了解 JavaScript 和 WebAssembly 的性能區別之前,需要先理解 JS 引擎的工作原理。

下面這張圖片介紹了性能使用的大概分布情況。

JS 引擎在圖中各個部分所花的時間取決于頁面所用的 JavaScript 代碼。圖表中的比例并不代表真實情況下的確切比例情況。

JS 引擎

圖中的每一個顏色條都代表了不同的任務:

  • Parsing——表示把源代碼變成解釋器可以運行的代碼所花的時間;
  • Compiling + optimizing——表示基線編譯器和優化編譯器花的時間。一些優化編譯器的工作并不在主線程運行,不包含在這里。
  • Re-optimizing——當 JIT 發現優化假設錯誤,丟棄優化代碼所花的時間。包括重優化的時間、拋棄并返回到基線編譯器的時間。
  • Execution——執行代碼的時間
  • Garbage collection——垃圾回收,清理內存的時間

這里注意:這些任務并不是離散執行的,或者按固定順序依次執行的。而是交叉執行,比如正在進行解析過程時,其他一些代碼正在運行,而另一些正在編譯。

這樣的交叉執行給早期 JavaScript 帶來了很大的效率提升,早期的 JavaScript 執行類似于下圖,各個過程順序進行:

早期的 JavaScript 執行類似于下圖

早期時,JavaScript 只有解釋器,執行起來非常慢。當引入了 JIT 后,大大提升了執行效率,縮短了執行時間。

JIT 所付出的開銷是對代碼的監視和編譯時間。JavaScript 開發者可以像以前那樣開發 JavaScript 程序,而同樣的程序,解析和編譯的時間也大大縮短。這就使得開發者們更加傾向于開發更復雜的 JavaScript 應用。

同時,這也說明了執行效率上還有很大的提升空間。

2. WebAssembly 對比

下面是 WebAssembly 和典型的 web 應用的近似對比圖:

各種瀏覽器處理上圖中不同的過程,有著細微的差別,拿 SpiderMonkey 作為例子。

3. 文件獲取

這一步并沒有顯示在圖表中,但是這看似簡單地從服務器獲取文件這個步驟,卻會花費很長時間。

WebAssembly 比 JavaScript 的壓縮率更高,所以文件獲取也更快。即便通過壓縮算法可以顯著地減小 JavaScript 的包大小,但是壓縮后的 WebAssembly 的二進制代碼依然更小。

這就是說在服務器和客戶端之間傳輸文件更快,尤其在網絡不好的情況下。

4. 解析

當到達瀏覽器時,JavaScript 源代碼就被解析成了抽象語法樹。

瀏覽器采用懶加載的方式進行,只解析真正需要的部分,而對于瀏覽器暫時不需要的函數只保留它的樁(stub,譯者注:關于樁的解釋可以在之前的文章中有提及)。

解析過后 AST (抽象語法樹)就變成了中間代碼(叫做字節碼),提供給 JS 引擎編譯。

而 WebAssembly 則不需要這種轉換,因為它本身就是中間代碼。它要做的只是解碼并且檢查確認代碼沒有錯誤就可以了。

5. 編譯和優化

在關于 JIT 的文章中,我有介紹過,JavaScript 是在代碼的執行階段編譯的。因為它是弱類型語言,當變量類型發生變化時,同樣的代碼會被編譯成不同版本。

不同瀏覽器處理 WebAssembly 的編譯過程也不同,有些瀏覽器只對 WebAssembly 做基線編譯,而另一些瀏覽器用 JIT 來編譯。

不論哪種方式,WebAssembly 都更貼近機器碼,所以它更快,使它更快的原因有幾個:

在編譯優化代碼之前,它不需要提前運行代碼以知道變量都是什么類型。

編譯器不需要對同樣的代碼做不同版本的編譯。

很多優化在 LLVM 階段就已經做完了,所以在編譯和優化的時候沒有太多的優化需要做。

編譯和優化

6. 重優化

有些情況下,JIT 會反復地進行“拋棄優化代碼<->重優化”過程。

當 JIT 在優化假設階段做的假設,執行階段發現是不正確的時候,就會發生這種情況。比如當循環中發現本次循環所使用的變量類型和上次循環的類型不一樣,或者原型鏈中插入了新的函數,都會使 JIT 拋棄已優化的代碼。

反優化過程有兩部分開銷。***,需要花時間丟掉已優化的代碼并且回到基線版本。第二,如果函數依舊頻繁被調用,JIT 可能會再次把它發送到優化編譯器,又做一次優化編譯,這是在做無用功。

在 WebAssembly 中,類型都是確定了的,所以 JIT 不需要根據變量的類型做優化假設。也就是說 WebAssembly 沒有重優化階段。

重優化

7. 執行

自己也可以寫出執行效率很高的 JavaScript 代碼。你需要了解 JIT 的優化機制,例如你要知道什么樣的代碼編譯器會對其進行特殊處理(JIT 文章里面有提到過)。

然而大多數的開發者是不知道 JIT 內部的實現機制的。即使開發者知道 JIT 的內部機制,也很難寫出符合 JIT 標準的代碼,因為人們通常為了代碼可讀性更好而使用的編碼模式,恰恰不合適編譯器對代碼的優化。

加之 JIT 會針對不同的瀏覽器做不同的優化,所以對于一個瀏覽器優化的比較好,很可能在另外一個瀏覽器上執行效率就比較差。

正是因為這樣,執行 WebAssembly 通常會比較快,很多 JIT 為 JavaScript 所做的優化在 WebAssembly 并不需要。另外,WebAssembly 就是為了編譯器而設計的,開發人員不直接對其進行編程,這樣就使得 WebAssembly 專注于提供更加理想的指令(執行效率更高的指令)給機器就好了。

執行效率方面,不同的代碼功能有不同的效果,一般來講執行效率會提高 10% - 800%。

執行

8. 垃圾回收

JavaScript 中,開發者不需要手動清理內存中不用的變量。JS 引擎會自動地做這件事情,這個過程叫做垃圾回收。

可是,當你想要實現性能可控,垃圾回收可能就是個問題了。垃圾回收器會自動開始,這是不受你控制的,所以很有可能它會在一個不合適的時機啟動。目前的大多數瀏覽器已經能給垃圾回收安排一個合理的啟動時間,不過這還是會增加代碼執行的開銷。

目前為止,WebAssembly 不支持垃圾回收。內存操作都是手動控制的(像 C、C++一樣)。這對于開發者來講確實增加了些開發成本,不過這也使代碼的執行效率更高。

WebAssembly 不支持垃圾回收

9. 總結

WebAssembly 比 JavaScript 執行更快是因為:

  • 文件抓取階段,WebAssembly 比 JavaScript 抓取文件更快。即使 JavaScript 進行了壓縮,WebAssembly 文件的體積也比 JavaScript 更小;
  • 解析階段,WebAssembly 的解碼時間比 JavaScript 的解析時間更短;
  • 編譯和優化階段,WebAssembly 更具優勢,因為 WebAssembly 的代碼更接近機器碼,而 JavaScript 要先通過服務器端進行代碼優化。
  • 重優化階段,WebAssembly 不會發生重優化現象。而 JS 引擎的優化假設則可能會發生“拋棄優化代碼<->重優化”現象。
  • 執行階段,WebAssembly 更快是因為開發人員不需要懂太多的編譯器技巧,而這在 JavaScript 中是需要的。WebAssembly 代碼也更適合生成機器執行效率更高的指令。
  • 垃圾回收階段,WebAssembly 垃圾回收都是手動控制的,效率比自動回收更高。

這就是為什么在大多數情況下,同一個任務 WebAssembly 比 JavaScript 表現更好的原因。

但是,還有一些情況 WebAssembly 表現的會不如預期;同時 WebAssembly 的未來也會朝著使 WebAssembly 執行效率更高的方向發展。這些我會在下一篇文章《WebAssembly 的現在與未來》中介紹。

點擊《WebAssembly 系列(四)WebAssembly 工作原理》《WebAssembly 系列(五)為什么 WebAssembly 更快?》閱讀原文。

【本文是51CTO專欄作者“胡子大哈”的原創文章,轉載請聯系作者本人獲取授權】

戳這里,看該作者更多好文

責任編輯:趙寧寧 來源: 51CTO專欄
相關推薦

2012-04-20 13:27:17

NFC

2017-03-19 22:43:12

WebAssemblyJavaScript編程

2024-04-16 00:13:52

JS網絡狀態ts類

2022-08-15 06:00:00

二進制編程語言

2023-05-05 17:20:04

2023-03-27 13:25:18

WebAssembl語言Scheme

2021-06-11 09:00:00

語言WebWebAssembly

2022-05-16 10:25:03

Web內部垃圾收集安全性

2020-11-03 08:12:20

WebAssemblyAPI

2023-01-31 09:02:24

JSVMVR

2022-06-02 08:01:11

云原生工具

2022-10-28 16:57:18

DockerWasm

2023-12-10 16:48:00

Wasm瀏覽器

2018-10-15 17:31:00

網絡安全病毒網絡攻擊

2022-06-22 10:04:29

JavaScriptRust語言

2022-01-16 20:25:57

WebAssembly網絡

2011-08-22 16:39:15

iOS內存

2018-07-30 13:29:04

WebAssemblyGo語言

2017-03-19 20:41:57

WebAssemblyJavaScript編程

2021-06-09 11:03:13

Web開發軟甲開發JavaScript
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久青青 | 91精品国产色综合久久不卡蜜臀 | 视频在线h | 你懂的国产| 国产9999精品| 五月婷婷激情网 | 欧美理论片在线 | 一区二区精品 | 欧美精品一区二区三区在线播放 | 三级特黄特色视频 | 在线a视频 | 欧美视频在线播放 | 日韩成人免费视频 | 精品视频在线一区 | 99热最新| av官网在线 | 狠狠的干 | 婷婷二区| 日韩在线中文字幕 | 欧美激情精品久久久久久 | 成人欧美一区二区三区白人 | 免费一区二区三区在线视频 | 久久久久久天堂 | 97国产精品 | 国产精品久久久久久238 | 国产婷婷在线视频 | 日日噜噜夜夜爽爽狠狠 | 欧美vide| 国产一区二区精品在线观看 | 亚洲精品一区二区三区中文字幕 | 亚洲网站观看 | 综合色播| 欧美成人精品一区二区男人看 | 黄网站免费在线看 | 国产一级淫片a直接免费看 免费a网站 | 欧美国产一区二区三区 | 亚洲精品不卡 | 国产精品1区2区 | 欧美日韩视频在线 | 国产精品射 | 国产亚洲高清视频 |