天天看點

使用 WebAssembly 将前端代碼速度提升20%!

作者:進階前端進階

大家好,很高興又見面了,我是"進階前端進階",由我帶着大家一起關注前端前沿、深入前端底層技術,大家一起進步,也歡迎大家關注、點贊、收藏、轉發!

使用 WebAssembly 将前端代碼速度提升20%!

進階前端進階

WebAssembly那些事

無論使用的是 Chrome、Firefox、Edge 還是 Safari浏覽器,代碼都是由 JavaScript 引擎解釋和執行的,即浏覽器隻運作 JavaScript。 不幸的是,JavaScript 并不總是開發者想要執行的每項任務的理想選擇,這就有了 WebAssembly 的用武之地。下面是MDN上對WebAssembly的描述:

WebAssembly or "wasm" is a new portable, size- and load-time-efficient format suitable for compilation to the web.

Chrome>57、Edge>16、Safari>11、FireFox>52、Opera>44等浏覽器都已經支援WebAssembly。WebAssembly 是一種可以在現代浏覽器中運作的新型代碼, 它的建立是為了能獲得更好的性能優勢。 它是一種更偏底層的二進制格式,體積小,是以加載和執行速度很快。 開發者也不用直接編寫 WebAssembly,而是可以通過将其他進階語言編譯成為WebAssembly。

WebAssembly(彙編)通常是指類似于機器代碼的人類可讀語言,機器代碼是處理器所了解的,即一堆數字。

使用 WebAssembly 将前端代碼速度提升20%!

WebAssembly到機器碼

為了在處理器上運作,每種進階程式設計語言都會被翻譯成機器代碼。不同類型的處理器架構需要不同的機器代碼和不同類型的彙編。

使用 WebAssembly 将前端代碼速度提升20%!

盡管名字叫 WebAssembly,但它并不完全是一種彙編語言,因為它并不适用于任何特定的機器。 它适用于浏覽器,當開發者傳遞要在浏覽器中執行的最終代碼時,開發者并不知道代碼将在哪種機器上運作,而WebAssembly可以做到開發者底層完全無感。

實際上,WebAssembly 是一種用于概念機器的語言, 當浏覽器下載下傳 WebAssembly 代碼時,它可以快速将其轉換為任何機器的程式集。比如:下圖展示了 WebAssembly 的格式,它具有易于閱讀的文本格式特性 (.wat),而右側的二進制表示是實際傳遞給浏覽器運作的内容 (.wasm)。

使用 WebAssembly 将前端代碼速度提升20%!

WebAssembly 文本和二進制格式

WebAssembly 能夠讓開發者将 C、C++ 或 Rust 代碼之類的東西編譯成所謂的 WebAssembly 子產品,同時可以将其加載到 Web 應用程式中并從 JavaScript 中調用它。但是需要聲明的是:WebAssembly不是 JavaScript 的直接替代品,它的設計理念是與JavaScript一起工作。

使用 WebAssembly 将前端代碼速度提升20%!

應用程式中的 WebAssembly 子產品

1.為什麼說Web是終态

Web的優勢在于它在任何地方都有效,避免下載下傳和安裝應用程式等繁瑣流程,進而實作立即傳遞。 它比直接在計算機上下載下傳和運作二進制檔案更安全,因為浏覽器建立了安全機制,可以防止其中運作的代碼幹擾系統。 同時,在 Web 上共享内容也非常容易 ,通過URL即可實作,開發者還可以将Web内容托管在任何地方。

使用 WebAssembly 将前端代碼速度提升20%!

Web是終态

Web是應用程式可在任何裝置上通路、共享的唯一真正通用的平台,進而允許開發者維護一個單一的代碼庫,同時保持更新迅速。

2.為什麼需要 WebAssembly

比如一些特定的場景:視訊遊戲、視訊編輯、3D 渲染或音樂制作等, 這些應用程式需要進行大量計算并且需要很高的性能,這種性能很難通過JavaScript得到保障。

JavaScript 最初隻是一種簡單的腳本語言,旨在為充滿輕量級超文本文檔的網絡帶來一些互動性。 它被設計為易于學習和編寫,但并不是為了速度而設計的。 多年來,浏覽器對它們解釋 JavaScript 的方式進行了優化,進而帶來了重大的性能改進。

使用 WebAssembly 将前端代碼速度提升20%!

随着JavaScript變得越來越快,開發者可以在浏覽器中執行的操作開始逐漸擴充。 新的 API 帶來了互動式圖形、視訊流、離線浏覽等功能。 反過來,越來越多以前僅限于本機的豐富應用程式開始出現在網絡上。 今天,使用者可以輕松地從浏覽器編輯文檔和發送電子郵件,但在某些領域,JavaScript 的性能仍然是無法磨滅的硬傷。

比如:視訊遊戲的性能局限一直極具挑戰性,因為它們不僅要協調音頻和視訊,而且通常還要協調實體和人工智能,而WebAssembly 可以解決這些問題。

3.WebAssembly優勢

3.1 速度

下載下傳執行速度

WebAssembly 專為速度而生,它的二進制檔案比文本 JavaScript 檔案小得多,進而下載下傳速度飛快,特别是在低速網絡場景。

WebAssembly的解碼和執行速度也更快。 JavaScript 是一種動态類型語言,變量類型不必預先定義,也不需要預先編譯。 這使得編寫代碼變得容易和快速,但這也意味着 JavaScript 引擎有更多的工作要做。 它必須在頁面上執行時解析、編譯和優化代碼。

解析 JavaScript 涉及将純文字轉換為稱為抽象文法樹 (AST) 的資料結構,并将其轉換為二進制格式。 WebAssembly 以二進制形式傳遞,解碼速度更快。 它是靜态類型的,是以與 JavaScript 不同,引擎不需要在編譯期間推測将使用什麼類型。

大多數優化發生在源代碼編譯期間,甚至在它進入浏覽器之前。 記憶體是手動管理的,就像 C 和 C++ 等語言一樣,是以也沒有垃圾收集。 所有這些都提供了更好、更可靠的性能。據統計, WASM 二進制檔案的執行時間僅比相同本機代碼的執行時間慢 20%。

使用 WebAssembly 将前端代碼速度提升20%!

在 JavaScript 引擎中處理 WebAssembly 所花費的相對時間

WebAssembly緩存機制

浏覽器為WebAssembly提供了很好的優化緩存機制,最大限度的提升浏覽器中WebAssembly的運作效率。比如下面使用流式API方法compileStreaming 或 instantiateStreaming 編譯或執行個體化 WebAssembly 子產品,優化WebAssembly 代碼緩存。

(async () => {
  const fetchPromise = fetch('fibonacci.wasm');
  const { instance } = await WebAssembly.instantiateStreaming(fetchPromise);
  const result = instance.exports.fibonacci(42);
  console.log(result);
})();           

下面使用的是instantiateStreaming方法:

(async () => {
  const response = await fetch('fibonacci.wasm');
  const module = await WebAssembly.compileStreaming(response);
  const instance = await WebAssembly.instantiate(module);
  const result = instance.exports.fibonacci(42);
  console.log(result);
})();           

注意:由于代碼緩存取決于資源 URL 以及 .wasm 資源是否最新,開發人員應盡量保持兩者穩定。 如果 .wasm 資源是從不同的 URL 擷取,它被認為是不同,浏覽器必須重新編譯子產品。 同樣,如果 .wasm 資源在緩存中不再有效,則 Chrome 必須丢棄所有緩存的代碼。

3.2 可移植性

要在裝置上運作應用程式,它必須與裝置的處理器架構和作業系統相容。 這意味着需要為想要支援的每種作業系統和 CPU 架構組合編譯源代碼。 使用 WebAssembly 隻需一個編譯步驟,應用程式就可以在每一種現代浏覽器中運作。

使用 WebAssembly 将前端代碼速度提升20%!

編譯本機代碼以在不同平台上運作與編譯為 WebAssembly

開發者不僅可以将自己的應用程式移植到 Web,還可以将現有的大量 C++ 庫和開源應用程式移植到 Web。 它是一種幾乎所有平台都支援的語言,包括 iOS 和 Android。 WebAssembly可以用作跨 Web 和移動部署的通用語言。

3.3 靈活性

到目前為止,JavaScript 一直是 Web 浏覽器中唯一完全受支援的語言。 有了 WebAssembly,Web 開發人員将能夠選擇其他語言,更多的開發人員将能夠為 Web 編寫代碼。 JavaScript 仍然是大多數用例的最佳選擇,但當确實需要性能提升時,可以選擇使用更加優秀的語言。 UI 和應用程式邏輯等部分用 JavaScript 編寫,核心功能在 WebAssembly 中。

使用 WebAssembly 将前端代碼速度提升20%!

根據MDN資料文檔資料,目前完全支援的語言是 C、C++ 和 Rust,但還有許多其他語言正在開發中,包括 Kotlin 和 .NET,這兩種語言都已經提供了實驗性支援。

4.使用WebAssembly

4.1 準備工作

如果您已經使用 Emscripten 等工具編譯了另一種語言的子產品,或者自己加載并運作代碼,那麼下一步是了解如何使用 WebAssembly JavaScript API 的其他功能。

Emscripten 是一個開源的編譯器,可以将 C/C++ 代碼編譯為 WebAssembly 程式設計語言的代碼。Emscripten 的底層是基于 LLVM 編譯器的,可以檢視其開源的 emscripten llvm 和 emscripten clang 。

讓我們通過一步一步的例子來了解如何在 WebAssembly 中使用 Javascript API和如何在網頁中加載一個 wasm 子產品。

  1. 首先需要一個 wasm 子產品,下載下傳 simple.wasm 檔案到本機的一個新的目錄下。
  2. 確定本機使用的是支援 webassembly 的浏覽器,Firefox 52+ 和 Chrome 57+ 是預設支援 webassembly 的。
  3. 然後,建立一個簡單的 HTML 檔案命名為 index.html 和并且你的本機的 wasm 檔案處于同一目錄下 ( 如果你沒有模闆可以使用我們提供的 simple template )
  4. 為了幫助了解發生了什麼,讓我們來看看這個 wasm 子產品的文本表示 (也可以在将 WebAssembly 文本格式轉換為 wasm見到):
(module
  (func $i (import "imports" "imported_func") (param i32))
  (func (export "exported_func")
    i32.const 42
        call $i))           

在第二行,你将看到導入有一個兩級命名空間 —— 内部函數 $i 是從 imports.imported_func 導入的。編寫要導入到 wasm 子產品的對象時,需要在 JavaScript 中反映這個兩級命名空間。建立一個 <script></script> 節點在你的 HTML 檔案中,并且添加下面的代碼:

var importObject = {
  imports: {
      imported_func: function(arg) {
        console.log(arg);
      }
    }
  };           

如上所述,在 imports.imported_func 中有我們導入的函數。

4.2 加載并使用 wasm 子產品

當導入了對象後,将擷取 wasm 檔案,使其在 array buffer 可用,然後就可以使用其導出的函數。下面添加以下代碼到你的腳本中:

fetch('simple.wasm')
.then(res =>
  res.arrayBuffer()
).then(bytes =>
  WebAssembly.instantiate(bytes, importObject)
).then(results => {
  results.instance.exports.exported_func();
});           

這樣做的結果是執行導出的 WebAssembly 函數 exported_func,又調用了另一個導入的 JavaScript 函數 imported_func, 它将 WebAssembly 執行個體中提供的值列印到控制台。

5.本文總結

本文主要和大家介紹WebAssembly,即浏覽器支援的除JavaScript外的新的語言,文章聚焦在什麼是WebAssembly、為什麼說Web是終态 、為什麼需要 WebAssembly 、WebAssembly優勢 、如何使用WebAssembly等次元。因為篇幅有限,文章并沒有過多展開,如果有興趣,文末的參考資料提供了優秀文檔以供學習。最後,歡迎大家點贊、評論、轉發、收藏!

參考資料

https://developer.chrome.com/blog/wasm-debugging-2020/

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

https://caniuse.com/?search=WebAssembly

https://www.codercto.com/a/59841.html

英文連結:https://blog.logrocket.com/webassembly-how-and-why-559b7f96cd71/

英文作者:Milica Mihajlija

翻譯作者:進階前端進階,有改動