縱論WebAssembly,JS在性能逆境下召喚強援


webassembly的作用 

webassembly是一種底層的二進制數據格式和一套可以操作這種數據的JS接口的統稱。我們可以認為webassembly的范疇里包含兩部分
  • wasm: 一種體積小、加載快並且可以在Web瀏覽器端運行的底層二進制數據格式,並且可以由C++等語言轉化而來

  • webassembly的操作接口:例如WebAssembly.instantiate就可以將一份wasm文件編譯輸出為JS能夠直接調用的模塊對象 

打破性能瓶頸
一直以來,我們都比較關心JS的運行速度問題,V8引擎解決了絕大多數情況下遇到的問題,但是少數情況下我們進行大量本地運算的時候,仍然可能遇到性能瓶頸,需要優化,這個時候webassembly的作用就凸現出來了 

webassembly項目的編碼流程

  • 性能無強關的部分用JS編寫

  • 性能強相關的,並且需要大量本地運算的部分,先用C++/Rust編寫,通過命令行工具轉化為wasm代碼后讓JS調用

 

 

 

 

玄學的webassembly性能提升

webassembly相對於純JS的性能提升是隨具體場景和條件的變化而變化的 

當您使用WebAssembly時,不要總是期望得到20倍的加速。您可能只得到2倍的加速或者20%的加速。或者,如果您在內存中加載非常大的文件時,或者需要在WebAssembly和JavaScript之間進行大量通信時,那么速度可能會變慢。 作者:Robert 《Level Up With WebAssembly》一書的作者,同時也是一位生物信息學軟件工程師

參考鏈接

在上面的文章的作者Robert,做了這樣一個實驗,他使用 seqtk,一個用C編寫的評估DNA測序數據質量(通常用於操作這些數據文件)的軟件,去對比webassembly相對於普通JS帶來的性能提升

 

一.Robert的對比測試結果
下面是他的測試結果
  • 第一步:運行序列分析軟件seqtk,對比性能:9倍提升

  • 第二步:刪除不必要的printf輸出,對比性能:13倍提升

  • 第三步:去除函數的重復調用后,對比性能:21倍提升

 
當然,上面的概括也許太過簡略,大家可以看看Robert的原文以得到更為詳細的認識
 
二.運行Fibonacci函數的性能對比
有位博主,對比了運行遞歸無優化的Fibonacci函數的時候,WebAssembly版本和原生JavaScript版本的性能差距,下圖是這兩個函數在值是45、48、50的時候的性能對比。

 

文章鏈接 作者:detectiveHLH

 

三.IVweb的的性能對比測試
IVWeb團隊對長度不同的文本進行加密處理,對比webassembly相對於純JS的性能提升,結果發現
  • 對於長文本(2M文本) 的密集計算,webassembly的性能提升很大

  • 對於短文本("IVWEB")的密集計算,webassembly和純JS性能相差無幾

第一組測試:2M長文本100000 次加密處理
 
第二組測試:"ivweb"短字符加密100000 次

 

 

 

資料來源

 

從上面的資料中我們了解到,webassembly性能提升的確存在,但是這個提升的范圍是隨條件和場景而變化的,需要遵循一定的原則

webassembly的兼容

下面是我在can i use上查到的結果,可以看到在現代瀏覽器上兼容良好,覆蓋率達到88%。主要的問題在於IE瀏覽器不支持(IE11) 

  

IE兼容解決方案
Internet Explorer 11 是最后一個占有很大的市場份額,但不支持wasm的瀏覽器。我們可以通過 binaryen 項目的 wasm2js 工具,將我們的 WebAssembly 編譯成 JavaScript,就可以獲得 IE11 的大部分支持了
 

實戰 WebAssembly

在瀏覽器中使用WebAssembly主要有兩種方式:
  • 編寫Rust代碼,然后通過wasm-pack轉化成wasm代碼

  • 編寫C/C++代碼,然后通過Emscripten轉化成wasm代碼

備注:Rust是一門高性能的系統編程語言

 

通過Rust接入WebAssembly

《Rust 和 WebAssembly 用例》

1.安裝rustup,初始化Rust環境,它會順帶安裝cargo等工具(相當於前端的Node安裝)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

2.安裝編譯工具wasm-pack(相當於前端的babel)

cargo install wasm-pack
3.創建一個文件夾,進入后運行下面代碼,初始化一個Rust 項目
cargo new --lib hello-wasm

初始化的文件夾如下所示 

 

4.修改lib.rs,改為以下幾段Rust代碼,這段代碼的is_odd是一個判斷數字是否為奇數的方法
extern crate wasm_bindgen;

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn is_odd(n: u32) -> bool {
    n % 2 == 1
}

 

5.修改配置文件Cargo.toml

這個文件和我們的package.json有點像,我們就依樣畫葫蘆,這個文件大概要寫成下面這個樣子 
[package]
name = "hello-wasm"
version = "0.1.0"
authors = ["作者名"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2"

 

備注
  • dependencies中必須要有wasm-bindgen這個依賴

  • 同時還要指定crate-type = ["cdylib"],否則轉化不能成功

6.運行以下命令進行編譯轉化
wasm-pack build --scope [自己的名字]
// My Example
wasm-pack build --scope penghuwan

 

編譯開始

 

編譯成功后,新增了pkg文件夾和target文件夾
 
讓我們看看pkg文件夾下的文件有哪
 
 
7. 將包發布到npm
1.cd pkg 
2.npm publish --access=public
8.安裝剛剛發布的wasm模塊,並通過webpack工具加載后,在瀏覽器運行以下代碼
const js = require("hello-wasm");
js.then(js => {
 const num1 = js.is_odd(3);
 const num2 = js.is_odd(4);
  console.log(num1);
  console.log(num2);
});

 

9.瀏覽器輸出 

 

 

通過C/C++接入WebAssembly

1.首先要按照文檔下載編譯工具emscripten

備注:如果沒有將source ./emsdk_env.sh寫入到啟動文件中的話,那么每次使用前都要在給定目錄下運行一遍 

2.創建一個文件h.c,寫入以下代碼
#include <stdio.h>

int main(int argc, char ** argv) {
 printf("Hello World");
}

 

3.用命令行編譯它

emcc h.c -s WASM=1 -o h.js

 

生成文件如下圖所示 
 
 
4.運行生成的h.js,則可看到輸出了Hello World

 

WebAssembly相關的接口 API

看了上面的案例,你可能會覺得有些奇怪:怎么我們沒有涉及瀏覽器提供的webassembly的API呀?
其實是有的,只不過在工具編譯的時候自動幫忙填寫了一些API而已,我們看下上面從h.c編譯出來的h.js的一些片段就知道了 

 

下面我們就來介紹下怎么手動去寫這些API
 
接口
>> WebAssembly.Instance
實例包含所有的 WebAssembly 導出函數 ,允許從JavaScript 調用 WebAssembly 代碼.
 
對象屬性
  • exports屬性: 一個對象,該對象包含從WebAssembly模塊實例導出的所有函數屬性

>> WebAssembly.Module 
包含已經由瀏覽器編譯的無狀態 WebAssembly 代碼,可以高效地與 Workers 共享、緩存在 IndexedDB 中,和多次實例化。 
 
對象屬性
  • exports屬性:一個數組,內容是所有已聲明的接口的描述。

  • imports屬性和:一個數組,內容是所有已聲明的引用的描述。

參考鏈接

 

方法
>> WebAssembly.instantiate
它是編譯和實例化 WebAssembly 代碼的主要方法
  • 參數:包含你想編譯的wasm模塊二進制代碼的ArrayBuffer的類型實例 

返回值: 一個Promise, resolve后的值如下所示
{
  module: 一個被編譯好的 WebAssembly.Module 對象. 
  instance: 一個WebAssembly.Instance對象
}

Example 

fetch('simple.wasm').then(response =>
  response.arrayBuffer()
).then(bytes =>
  WebAssembly.instantiate(bytes)
).then(result =>
  result.instance.exports // exports是wasm中輸出的
);

 

webassembly的未來展望

  • 多線程

  • SIMD(單指令流多數據流)

  • 64位尋址

  • 流式編譯(在下載的同時編譯 WebAssembly 文件)

  • 分層編譯器

  • 隱式 HTTP 緩存 

參考文章

 

webassembly的使用場景及其限制

之前我們已經說到,webassembly適用於JS難以解決的大計算量的應用場景,如圖像/視頻編輯、計算機視覺,3D游戲等等。在這些場景下,webassembly能夠大限度地提高速度,彌補JS的缺陷和硬傷。
 
同時在另一方面,我們也需要認識到以下幾點:
  1. 其實在大多數場景下我們都不需要用到webassembly。因為V8等JS引擎的優化帶來了巨大的性能提升,已經足夠讓JS應對絕大多數的普通場景了,所以只有在以上的少數場景下,我們才需要做這種“二次提升”

  2. 和很多其他特性一樣,兼容性同樣是webassembly的一道坎,現代瀏覽器雖然支持度良好,但是在國內IE泛濫的特殊情況下, 這仍然是對webassembly的一個挑戰。不過在桌面應用上或者一些對兼容性要求較低的工具型網頁運用上,webassembly已經生根發芽,甚至能夠遍地開花。

webassembly的產品案例

 

設計工具Figma
一般情況下,為了使用速度,設計工具都會選擇Adobe等本地應用,而不會選擇瀏覽器網頁應用,而能夠同時打開十幾個畫板也沒有卡頓的Figma正在嘗試改變這一認知,webassembly讓它具有高效流暢的體驗 
 
白鷺游戲引擎
白鷺游戲引擎是一套HTML5游戲開發解決方案,它衍生了開發莽荒紀同名手游、夢道、坦克風雲的等游戲,而利用 WebAssembly,白鷺引擎讓游戲運行性能提升了300%。

 

 

 
OpenGL 圖形引擎Magnum
Magnum 是一款數據可視化 OpenGL 圖形處理引擎,也采用了WebAssembly支撐瀏覽器環境的應用

 

參考資料


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM