AssemblyScript 入門指南

做者:Danny Guo

翻譯:瘋狂的技術宅javascript

原文:https://blog.logrocket.com/th...html

未經容許嚴禁轉載前端

WebAssembly(Wasm)是 Web 瀏覽器中相對較新的功能,但它地擴展了把 Web 做爲服務應用平臺的功能潛力。java

對於 Web 開發人員來講,學習使用 WebAssembly 可能會有一個艱難的過程,可是 AssemblyScript 提供了一種解決方法。首先讓咱們看一下爲何 WebAssembly 是一項頗有前途的技術,而後再看怎樣 AssemblyScript 挖掘潛力。node

WebAssembly

WebAssembly 是瀏覽器的低級語言,爲開發人員提供了除 JavaScript 以外的 Web 編譯目標。它使網站代碼能夠在安全的沙盒環境中以接近本機的速度運行。c++

它是根據全部主流瀏覽器(Chrome,Firefox,Safari 和 Edge)所表明的意見開發的,他們達成了設計共識,這些瀏覽器如今都支持 WebAssembly。git

WebAssembly 以二進制格式交付,這意味着與 JavaScript 相比,WebAssembly 在大小和加載時間上都具備優點。可是它也有易於理解的文本表示形式程序員

當 WebAssembly 首次發佈時,一些開發人員認爲它有可能最終取代 JavaScript 做爲 Web 的主要語言。可是最好把 WebAssembly 看做是與現有 Web 平臺良好集成的新工具,這是它的高級目標github

WebAssembly 並無取代 JavaScript 現有的用例,而是吸引了更多人,由於它引入了新的用例。 目前 WebAssembly 還不能直接訪問 DOM,大多數網站都但願使用 JavaScript,通過多年的優化,JavaScript 已經至關快了。如下 WebAssembly 可能的使用案例列表的示例:golang

  • 遊戲
  • 科學的可視化和模擬
  • CAD應用
  • 圖像/視頻編輯

這些應用共同特色是,它們一般會被看做是桌面應用。經過爲 CPU 密集型任務提供接近本機的性能,WebAssembly 使得將這些程序遷移至 Web 成爲可行。

現有網站也能夠從 WebAssembly 中受益。 Figma(https://www.figma.com/) 提供了一個真實的例子,它經過使用 WebAssembly 大大縮短了其加載時間。若是網站使用進行大量計算的代碼,則能夠將其替換爲 WebAssembly 以提升性能。

也許如今你對怎樣使用 WebAssembly 感興趣。你能夠學習語言自己並直接編寫,但實際上它打算成爲其餘語言的編譯目標。它被設計爲對 C 和 C++ 具備良好的支持,Go語言在 version 1.11 中增長了實驗性支持的版本中,Rust 也對其進行了大量投入

可是也許你並不想爲了使用 WebAssembly 而學習或使用其中某種語言。這就是 AssemblyScript 存在的意義。

AssemblyScript

AssemblyScript 是一個把 TypeScript 轉換到 WebAssembly 的編譯器。由微軟開發的 TypeScript 將類型添加到了 JavaScript 中。它已經變得至關受歡迎,即便對於不熟悉它的人,AssemblyScript 只容許 TypeScript 的有限功能子集,所以不須要花太多時間就能夠上手。。

由於它與 JavaScript 很是類似,因此 AssemblyScript 使 Web 開發人員能夠輕鬆地將 WebAssembly 整合到他們的網站中,而沒必要使用徹底不一樣的語言。

試用

讓咱們編寫第一個 AssemblyScript 模塊(如下全部代碼都可在 GitHub 上找到)。咱們須要 Node.js 的最低版本爲 8 才能獲得 WebAssembly 的支持

轉到一個空目錄,建立一個 package.json 文件,而後安裝 AssemblyScript。請注意,咱們須要直接從它的 GitHub 存儲庫安裝。它還沒有在 npm 上發佈,由於 AssemblyScript 開發人員尚未考慮編譯器是否已經準備好可以支持普遍使用。

mkdir assemblyscript-demo
cd assemblyscript-demo
npm init
npm install --save-dev github:AssemblyScript/assemblyscript

使用 asinit 命令生成腳手架文件:

npx asinit .

咱們的 package.json 如今應該包含如下腳本:

{
  "scripts": {
    "asbuild:untouched": "asc assembly/index.ts -b build/untouched.wasm -t build/untouched.wat --sourceMap --validate --debug",
    "asbuild:optimized": "asc assembly/index.ts -b build/optimized.wasm -t build/optimized.wat --sourceMap --validate --optimize",
    "asbuild": "npm run asbuild:untouched && npm run asbuild:optimized"
  }
}

頂層的 index.js 看起來像這樣:

const fs = require("fs");
const compiled = new WebAssembly.Module(fs.readFileSync(__dirname + "/build/optimized.wasm"));
const imports = {
  env: {
    abort(_msg, _file, line, column) {
       console.error("abort called at index.ts:" + line + ":" + column);
    }
  }
};
Object.defineProperty(module, "exports", {
  get: () => new WebAssembly.Instance(compiled, imports).exports
});

它使咱們可以像使用普通的 JavaScript 模塊同樣輕鬆地 require WebAssembly 模塊。

assembly 目錄中包含咱們的 AssemblyScript 源代碼。生成的示例是一個簡單的加法函數。

export function add(a: i32, b: i32): i32 {
  return a + b;
}

函數簽名就像在 TypeScript 中那樣,它之因此使用 i32 的緣由是 AssemblyScript 使用了 WebAssembly 的特定整數和浮點類型,而不是 TypeScript 的通用 number 類型

讓咱們來構建示例。

npm run asbuild

build 目錄如今應包含如下文件:

optimized.wasm
optimized.wasm.map
optimized.wat
untouched.wasm
untouched.wasm.map
untouched.wat

咱們獲得了構建的普通版本和優化版本。對於每一個構建版本,都有一個 .wasm 二進制文件,一個 .wasm.map 源碼映射,以及二進制文件的 .wat 文本表示形式。文本表示形式是爲了供人閱讀,但如今咱們無需閱讀或理解它——使用 AssemblyScript 的目的之一就是咱們不須要使用原始 WebAssembly。

啓動 Node 並像其餘模塊同樣使用編譯模塊。

$ node
Welcome to Node.js v12.10.0.
Type ".help" for more information.
> const add = require('./index').add;
undefined
> add(3, 5)
8

這就是從 Node 調用 WebAssembly 所須要的所有!

添加監視腳本

爲了便於開發,我建議你在每次更改源代碼時都用 onchange 自動重建模塊,由於 AssemblyScript 尚不包括監視模式

npm install --save-dev onchange

package.json 中添加一個 asbuild:watch 腳本。包含 -i flag,便可在運行命令後當即運行初始構建。

{
  "scripts": {
    "asbuild:untouched": "asc assembly/index.ts -b build/untouched.wasm -t build/untouched.wat --sourceMap --validate --debug",
    "asbuild:optimized": "asc assembly/index.ts -b build/optimized.wasm -t build/optimized.wat --sourceMap --validate --optimize",
    "asbuild": "npm run asbuild:untouched && npm run asbuild:optimized",
    "asbuild:watch": "onchange -i 'assembly/**/*' -- npm run asbuild"
  }
}

如今你能夠運行 asbuild:watch,而沒必要不斷地從新運行 asbuild

性能

讓咱們寫一個基本的基準測試,用來了解究竟能夠得到什麼樣的性能提高。 WebAssembly 的專長是處理諸如數字計算之類的 CPU 密集型任務,因此咱們用一個函數來肯定整數是否爲質數。

咱們的參考實現以下所示。這是一種幼稚的暴力解決方案,由於咱們的目標是執行大量計算。

function isPrime(x) {
    if (x < 2) {
        return false;
    }

    for (let i = 2; i < x; i++) {
        if (x % i === 0) {
            return false;
        }
    }

    return true;
}

等效的 AssemblyScript 版本僅須要一些類型註釋:

function isPrime(x: u32): bool {
    if (x < 2) {
        return false;
    }

    for (let i: u32 = 2; i < x; i++) {
        if (x % i === 0) {
            return false;
        }
    }

    return true;
}

咱們將使用 Benchmark.js

npm install --save-dev benchmark

建立benchmark.js

const Benchmark = require('benchmark');

const assemblyScriptIsPrime = require('./index').isPrime;

function isPrime(x) {
    for (let i = 2; i < x; i++) {
        if (x % i === 0) {
            return false;
        }
    }

    return true;
}

const suite = new Benchmark.Suite;
const startNumber = 2;
const stopNumber = 10000;

suite.add('AssemblyScript isPrime', function () {
    for (let i = startNumber; i < stopNumber; i++) {
        assemblyScriptIsPrime(i);
    }
}).add('JavaScript isPrime', function () {
    for (let i = startNumber; i < stopNumber; i++) {
        isPrime(i);
    }
}).on('cycle', function (event) {
    console.log(String(event.target));
}).on('complete', function () {
    const fastest = this.filter('fastest');
    const slowest = this.filter('slowest');
    const difference = (fastest.map('hz') - slowest.map('hz')) / slowest.map('hz') * 100;
    console.log(`${fastest.map('name')} is ~${difference.toFixed(1)}% faster.`);
}).run();

在個人機器上,運行 node benchmark 時獲得瞭如下結果:

AssemblyScript isPrime x 74.00 ops/sec ±0.43% (76 runs sampled)
JavaScript isPrime x 61.56 ops/sec ±0.30% (64 runs sampled)
AssemblyScript isPrime is ~20.2% faster.

請注意,這個測試是一個 microbenchmark,咱們應該謹慎閱讀。

對於一些更多的 AssemblyScript 基準測試,我建議你查看 WasmBoy 基準測試波動方程式基準測試

加載模塊

接下來,在網站中使用咱們的模塊。

先建立 index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>AssemblyScript isPrime demo</title>
    </head>
    <body>
        <form id="prime-checker">
            <label for="number">Enter a number to check if it is prime:</label>
            <input name="number" type="number" />
            <button type="submit">Submit</button>
        </form>

        <p id="result"></p>

        <script src="demo.js"></script>
    </body>
</html>

再建立 demo.js。加載 WebAssembly 模塊有多種方式,可是最有效的方法是經過使用 WebAssembly.instantiateStreaming 函數以流的方式編譯和實例化。請注意,若是 assertion 失敗的話,咱們須要提供 abort 函數

(async () => {
    const importObject = {
        env: {
            abort(_msg, _file, line, column) {
                console.error("abort called at index.ts:" + line + ":" + column);
            }
        }
    };
    const module = await WebAssembly.instantiateStreaming(
        fetch("build/optimized.wasm"),
        importObject
    );
    const isPrime = module.instance.exports.isPrime;

    const result = document.querySelector("#result");
    document.querySelector("#prime-checker").addEventListener("submit", event => {
        event.preventDefault();
        result.innerText = "";
        const number = event.target.elements.number.value;
        result.innerText = `${number} is ${isPrime(number) ? '' : 'not '}prime.`;
    });
})();

如今安裝 static-server。由於要使用WebAssembly.instantiateStreaming,咱們須要建立服務,該模塊須要使用 MIME typeapplication/wasm

npm install --save-dev static-server

將腳本添加到 package.json 中。

{
  "scripts": {
    "serve-demo": "static-server"
  }
}

運行 npm run serve-demo 並在瀏覽器中打開 localhost URL。提交表單中的數字,你將收到一條消息,指出該數字是否爲素數。如今,咱們已經實現了從用 AssemblyScript 編碼到在網站中實際使用的整個過程。

結論

WebAssembly 以及經過 AssemblyScript 的擴展,不會使每一個網站都神奇地變得更快,可是這並不重要。 WebAssembly 之因此使人興奮,是由於它可使更多的應用在 Web 變得中可行。

相似地,AssemblyScript 使更多開發人員可使用 WebAssembly,這使咱們很容易默認使用 JavaScript,可是當須要大量運算工做時,能夠用 WebAssembly。


本文首發微信公衆號:前端先鋒

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章


歡迎繼續閱讀本專欄其它高贊文章:


相關文章
相關標籤/搜索