Web程序性能優化——asm.js和WebAssembly

asm.js

asm.jsJavaScript語言中一個能夠高度優化的子集。經過避免JavaScript引擎某些難以優化的機制和模式(主要是垃圾回收和類型判斷),達到JavaScript引擎運行優化的目的。換言之,正常的JavaScript代碼會類型自動裝換和垃圾自動回收的,而編寫asm.js風格的代碼則表示程序員須要管理內存和肯定數據類型。前端

asm.js不提供任何額外的語法,只要編寫asm.js的代碼,在支持asm.js優化的JavaScript引擎中能被自動識別,從而讓引擎實現本身的優化,而在不支持asm.js的引擎中也能正常運行。ios

手寫asm.js例子

首先解決的是JavaScript中變量類型的問題c++

var a = 10;
var b = a;
複製代碼
var a = 10;
var b = a | 0;
複製代碼

上面兩段代碼的不一樣之處在於,第一段代碼b的值類型在運行的時候才能被肯定,而第二段代碼中b老是會被看成32位整型處理。一樣的還有對運算的限制,如(a + b) | 0把兩個變量的加運算限制爲更高效的整型加運算。在支持asm.jsJavaScript引擎中,運行上面的代碼會進行底層優化。git

第二個要解決的問題是內存分配,衆所周知,JavaScript不提供垃圾回收相關的API,在JavaScript中講垃圾回收大多數開發者會想到將不用的變量設爲null,好比:程序員

// 建立一個長度爲10000的數組
var a = new Array(10000).fill('hello');

a = null;
複製代碼

但對於垃圾回收a = null只是告訴瀏覽器長度爲10000的數組已經沒有被任何變量引用,能夠回收其佔用的內存了,至於何時回收,瀏覽器有本身的想法。github

asm.js中所說的內存分配和這個機制不一樣,asm.js的內存分配由程序員本身控制。使用ArrayBuffer建立一個數據緩衝區,能夠在這個緩衝區存儲和取值,而不須要再付出內存分配和垃圾回收的代價。ArrayBuffer是不能直接操做的,而是經過類型數組對象和DataView(視圖)對象,具體用法請參考 MDN | ArrayBuffer,下面是一個例子web

// 建立一個64k的數據緩衝區
var heap = new ArrayBuffer(0x10000);

// 使用64位浮點值數組引用這個緩衝區
var arr = new Float64Array( heap )

複製代碼

下面是一個更復雜的例子,使用asm.js風格的代碼編寫一個函數計算兩數之間的相鄰數的乘積,存儲起來並計算總和編程

function ASM (heap) {

  var arr = new Int32Array(heap);

  function foo(x, y) {
    x = x | 0;
    y = y | 0;

    var i = 0;
    var p = 0;
    var sum = 0;
    for (i = x | 0; (i | 0) < (y | 0); p = (p + 8) | 0, i = (i + 1) | 0) {
      sum = (sum + i * (i + 1)) | 0;

      arr[p >> 3] = (i * (i + 1)) | 0;
    }

    return +sum;
  }

  return foo;
}

var heap = new ArrayBuffer(0x1000);

var foo = ASM(heap)

foo(0, 1024) // 357913600

複製代碼

一般用「模塊」機制將asm.js代碼封裝起來,如上面的代碼,內存區變量爲私有變量,不可在外部更改。 上面代碼中,使用ArrayBuffer分配內存,使用Int32Array規定數據類型,在使用數據時,每一個數據也都使用特殊的符號標識數據類型,在支持asm.js的引擎中,這些都是觸發優化的信號,而在不支持asm.js的引擎,這些符號也是正常的運算符而已,不影響計算結果。小程序

Emscripten

在實際運用中,不大可能手寫asm.js規範的代碼,寫起來異常麻煩而且容易出錯,因此一般asm.js代碼一般是其餘語言的編譯目標代碼,好比使用EmscriptenC / C++代碼編譯成asm.js微信小程序

安裝Emscripten

$ git clone https://github.com/juj/emsdk.git
$ cd emsdk
$ ./emsdk install latest
$ ./emsdk activate latest
$ source ./emsdk_env.sh

複製代碼

Emscripten的編譯用法很是簡單

  1. 編寫一個C++程序hello.cc
#include <iostream>

int main () {
  std::cout << "Hello World" << std::endl;
}
複製代碼
  1. 使用如下命令將C++源碼編譯成asm.js
emcc hello.cc
複製代碼

輸出文件a.out.js就是asm.js規範的JavaScript代碼,默認執行main函數。

WebAssembly

WebAssembly字節碼是一種抹平了不一樣CPU架構的機器碼,WebAssembly字節碼不能直接在任何一種CPU架構上運行,但因爲很是接近機器碼,能夠很是快的被翻譯爲對應架構的機器碼,所以WebAssembly運行速度和機器碼接近,這聽上去很是像Java字節碼。

WebAssembly出現以前,瀏覽器只能運行.js後綴的編程代碼文件,JavaScript是web應用開發的惟一語言,可是在支持WebAssembly的瀏覽器上,如今能運行.wasm後綴的代碼文件了。

WebAssembly幾乎不多是手工編寫的,通常由其餘語言編譯而成,目前能編譯成WebAssembly的高級語言有:

  • AssemblyScript: 語法和TypeScript一致,對前端來講學習成本低,爲前端編寫WebAssembly最佳選擇;
  • c\c++: 官方推薦的方式;
  • Rust: 語法複雜、學習成本高,對前端來講可能會不適應;
  • Kotlin: 語法和 JavaJS 類似,語言學習成本低;
  • Golang: 語法簡單學習成本低。

Hello world

使用AssemblyScript編譯成WebAssembly,首先安裝

yarn global add AssemblyScript/assemblyscript
複製代碼

編寫源代碼demo.ts

export function foo (x: i32):i32 {
  return x * x;
}
複製代碼

使用asc demo.ts -o demo.wasm編譯代碼,使用js代碼fetch方法加載wasm模塊

fetch('./demo.wasm')
  .then(res => {return res.arrayBuffer()})
  .then(WebAssembly.instantiate) // 編譯成當前CPU架構的機器碼並實例化
  .then(module => { // module爲WebAssembly模塊
    console.log(module.instance.exports.foo(100))
  })
複製代碼

總結

asm.jsWebAssembly都是底層優化web程序性能的技術,他們一般都是由其餘語言編譯而成。asm.js是JavaScript的一個子集,因此在不支持asm.js優化的瀏覽器上也能正常運行,它的文件類型是文本;WebAssembly則是更新的技術,提供了新的API,在不支持的瀏覽器上沒法運行,它的文件類型是二進制字節碼。這兩種技術雖然都是極高提高web程序性能的技術,但通常開發中不會使用到,只有在密集型計算、圖形處理等計算場景才能發揮出它們的巨大優點。

做者簡介:葉茂,蘆葦科技web前端開發工程師,表明做品:口紅挑戰網紅小遊戲、蘆葦科技官網。擅長網站建設、公衆號開發、微信小程序開發、小遊戲、公衆號開發,專一於前端框架、服務端渲染、SEO技術、交互設計、圖像繪製、數據分析等研究。

歡迎和咱們一塊兒並肩做戰: web@talkmoney.cn 訪問 www.talkmoney.cn 瞭解更多

相關文章
相關標籤/搜索