iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 22
2
Modern Web

用 Javascript 當個影像魔術師系列 第 22

Day 22 - Canvas 效能調整 - Webassembly (上)

動態語言與靜態語言

相信大家都知道 Javascript 是一個動態語言,也就是說瀏覽器在執行的時候其實會需要多一個步驟去將程式碼轉譯為機器可以讀懂的行為,而這個轉譯也會依據不一樣的瀏覽器有不一樣的行為 ( 像大家最常聽到的應該就是 Chrome V8 ),相較於其他靜態語言如 C 是在執行前就已經被編譯好,動態語言是在執行的時候在去做解析,也因為如此效能上通常會比不上已經預先編譯好的程式碼。也因此大量的複雜計算一直不是 Javascript 的強項,於是為了達成 Web 統一天下的夢想, WebAssembly 被發明了出來

WebAssembly

WebAssembly 是一種標準,通常是被其他語言當作轉譯的目標如 CRustGo ,將程式碼轉成 bytecode 來執行,而因為省略了上面動態編譯的部分,所以可以帶來接近原生的速度。

而這次會從 Rust 開始試著編譯成 WebAssembly,原因有幾個

  1. 連續好幾年被選為程序員 最受喜愛的語言,看起來很厲害,順便學一下
  2. 大概搜尋了一下,發現對 WebAssembly 的生態蠻豐富的, wasm-bindgenJavascript 的一些互動都有做基本的包裝
  3. 因為 Rust 的爸爸是 Mozilla,同時也是 WebAssembly 的大力推動者,跟著走準沒錯!

Rust

那我們接下來就開始來進行吧,首先要先安裝 Rust 請參考 官網完成,裝完之後應該也會順便完成安裝 CargoRustCargo 的關係就像 NodeNpm 的關係一樣,是負責套件管理使用,接下來就可以來實際創建我們的專案囉,在原本的架構裡面新增 wasm 的資料夾,架構如下

Cargo.tomlCargo.lock 就像是 package.jsonpackage-lock.json,控制了要安裝哪些套件及版本,而 pkg 則是最後產出的檔案,src 則是程式碼。最後的 toml 檔應該會長這樣

[package]
name = "wasm"
version = "0.1.0"
authors = ["xxxxx"]
edition = "2018"

[lib]
crate-type = ["cdylib", "rlib"]

[features]
default = ["console_error_panic_hook"]

[dependencies.wasm-bindgen]
version = "^0.2"
features = ["serde-serialize"]


[dependencies]
serde = "^1.0.59"
serde_derive = "^1.0.59"
console_error_panic_hook = { version = "0.1.1", optional = true }
wee_alloc = { version = "0.4.2", optional = true }
[dev-dependencies]
wasm-bindgen-test = "0.2"

[profile.release]
# 下面的設定會跟最後的效能有關係
opt-level = 3
lto = true
panic = 'abort'

主要就是使用了 wasm-bindgen,接著在創建一個 lib.rs,寫一個簡單的費式數列,跟 Javascript 不一樣的是 Rust 對於傳入傳出的型態都會嚴格定義,而 match 的用法就是 switch 一樣,就這樣完成了我們的第一個函數

// 引入模組
extern crate wasm_bindgen;

#[wasm_bindgen]
pub fn fib(i: u32) -> u32 {
    match i {
        0 => 0,
        1 => 1,
        _ => fib(i-1) + fib(i-2)
    }
}

接著需要設定一下 Webpack,在 configplugins 加入這段,forceMode 會讓 Rust 產出 production 或著 dev 的版本,在最後要實際使用前記得更換,不然會發生效能差異很大。

  new WasmPackPlugin({
      crateDirectory: path.resolve(__dirname, './wasm'),
      forceMode: 'production'
    })

接著引入使用,因為現在還是必須非同步引入,所以我們偷懶直接這樣用,在引入後直接掛載到全域

const wasm = import('../wasm/pkg')

wasm
  .then(m => {
    console.log('load wasm success')
    Vue.prototype.$wasm = m
    new Vue({
      el: '#app',
      router,
      store,
      render: h => h(App)
    })
  })
  .catch(e => {
    console.log('wasm load error', e)
  })

接著就可以在裡面使用了,來看一下兩個語言版本的速度差了多少吧,用先前使用的費式數列當作計算

const t1 = performance.now()
// fib(43)
this.$wasm.fib(43)
const t2 = performance.now()
console.log('time', (t2 - t1) / 1000)

在桌機板的 chrome 上,Javascript 大約耗時 5.2秒,wasm 大約耗時 3.2秒,速率大概提升了快 40%,根據這篇文章,在大部分的情況下,大約是提升 30% 速度,但這又會依據瀏覽器、裝置而有不同的差異,例如在 Firefox 就有可能提升將近 90%。

小結

今天簡單介紹了 WebAssembly,看起來執行速度很厲害,但會因此取代 Javascript 嗎?
我是覺得他會變成輔助 Javascript 的角色,當有需要複雜計算的時候就會使用 WebAssembly 的模組,雖然現在引用上還不是很方便,但希望哪天就可以像在 npm 安裝一樣,輕鬆的就可以使用了。而且最大的好處就是他可以從其他語言轉換,也就是說像是有古早積累的大公司可以直接轉換,不必因為要搬去 Web 而整個重改。現實生活中已經在使用的有 autocadebay,可以想像的是未來會有更多原本因為效能限制而無法在網頁環境出現的應用會因此而蓬勃發展,也許哪天,雖然掛著前端工程師,但是寫的不在是 Javascript 了呢。


上一篇
Day 21 - Canvas 效能調整 - OffscreenCanvas 及 ImageBitmap (下)
下一篇
Day 23 - Canvas 效能調整 - Webassembly (下)
系列文
用 Javascript 當個影像魔術師30

尚未有邦友留言

立即登入留言