Javascript的攪局者—Webassembly

最近在破解網站驗證碼的時候,圖像識別速度上遇到一點瓶頸。按照我如今的代碼,從獲取到驗證碼圖片到輸出正確驗證碼字符串須要等待3秒的時間,可是3秒以後破解完黃花菜都涼了,因此我想有沒有什麼方法讓程序執行的快一點,最後目光聚焦在了Webassembly,因此入門學習了一下Webassembly。javascript

Webassembly是什麼?

Webassembly顧名思義web+assembly,web版的彙編語言,其實它並非一種語言,它只是爲高級語言(諸如C、C++和Java)提供一個高效的編譯目標。之因此和彙編能扯上關係,是由於它是接近計算機機器碼的二進制字節碼,而且能夠在現代瀏覽器直接運行。html

Webassembly有什麼優點?

衆所周知javascript是被十天設計出來的弱類型語言,整個web的性能優化史就是一個js的填坑史,JIT引擎讓js的執行速度快了10倍,爲了彌補JIT引擎的缺點,又出現了asm.js、TypeScript等js子集,至此web性能已經很高,可是人類的慾望是沒有止境的,瀏覽器廠商仍是再想怎麼可讓web速度更快,因而Webassembly橫空出世, 相比js,Webassembly體積更小、加載更快,兼容性強,執行速度快,這也是我所須要的。前端

Webassembly的兼容性?

使用一個新技術的時候咱們首先得看一下它在瀏覽器上的兼容性。進入Webassembly的官方網站,在導航條下方醒目的展現着「Webassembly 1.0 has shipped in 4 major browser engines. 」。代表現代瀏覽器對Webassembly的支持很是友好。java

再看一下具體瀏覽器支持狀況:node

從上面這張圖能夠看出各大瀏覽器對Webassembly的支持很好,PC端和手機端瀏覽器對Webassembly的支持率已經達到87.42%。同時NodeJS也已經全面支持Webassembly,我能夠放心大膽的使用了。 ### Webassembly的如何使用? 前面已經說過Webassembly不是一種語言,而是一種編譯目標,因此首先得找一個能夠生成這個編譯目標的編譯工具,目前能夠生成Webassembly的工具不少,Emscripten(它能夠將C/C++編譯成Webassembly)、AssemblyScript(它能夠將TypeScript編譯成Webassembly)、Binaryen(它能夠將asm.js編譯成Webassembly)、TeaVM(它能夠將Java字節碼編譯成Webassembly)等。由於對TypeScript比較熟悉,因此選用AssemblyScript做爲生成Webassembly的工具。 1. 新建一個NPM的項目 創建一個名爲wasmTest的目錄,目錄裏包含一個src文件夾index.html和package.json,目錄結構入下圖所示:
  1. 安裝AssemblyScript 去npm搜了一下AssemblyScript模塊,發現AssemblyScript 的 npm 官方模塊已經中止維護(搞不懂爲何會中止維護),那隻好直接從 Github 安裝AssemblyScript 的模塊。

在 package.json 的依賴加入 AssemblyScript 模塊的 Github 來源。git

"devDependencies": {
    "assemblyscript": "github:assemblyscript/assemblyscript"
}
複製代碼

執行cnpm install,等待安裝完成用asc來看一下是否安裝成功。若是顯示asc的使用命令行,則說明安裝成功。github

也能夠將AssemblyScript的Github庫clone到本地,使用npm link的方式來全局安裝AssemblyScript模塊。
  1. 寫原始代碼 在src目錄下新建一個index.ts,寫一個計算平方和的方法吧
export function sqart (a: number): number {
    return a * a;
}
複製代碼
  1. 編譯生成wasm文件 能夠直接在終端裏用asc命令+index.ts目錄的方式生成wasm文件,爲了後面運行起來簡單,我將命令寫到npm script腳本里,打開根目錄的package.json文件,將script字段變爲:
"scripts": {
    "build": "npm run build:optimized",
    "build:optimized": "asc src/index.ts -t dist/module.optimized.wat -b dist/module.optimized.wasm --optimize"
  }
複製代碼

--optimize 表明編譯時須要優化,在項目根目錄執行npm run build命令開始編譯,最終在dist目錄下生成module.optimized.wasm和module.optimized.wat兩個文件,它們分別是WebAssembly字節碼文件和WebAssembly文本文件。編譯完成的目錄結構如圖所示:web

  1. 在NodeJs中使用wasm 在根目錄下新建nodejs目錄,在nodejs目錄新建index.js和module.js,在module.js裏引入wasm模塊,代碼以下:
const fs = require("fs");
const path = require('path');

const env = {
	memoryBase: 0,
	tableBase: 0,
	memory: new WebAssembly.Memory({
		initial: 256
	}),
	table: new WebAssembly.Table({
		initial: 2,
		element: 'anyfunc'
	}),
	abort: () => {throw 'abort';}
}

const wasm = new WebAssembly.Module(
    fs.readFileSync(path.join(__dirname, "..", "/dist/module.optimized.wasm"))
);

const mod =  new WebAssembly.Instance(wasm, {env: env})

module.exports = mod.exports;
複製代碼

而後在index.js裏使用封裝好的wasm模塊,npm

var myModule = require("./module.js");

console.log("3 sqart is: ", myModule.sqart(3));
複製代碼

在根目錄下運行node ./nodejs/index.js,輸出下面結果:編程

  1. 在瀏覽器中使用wasm 在根目錄下新建index.js,如今項目的目錄結構以下:

在index.js裏引入wasm模塊,代碼以下:

const env = {
    memoryBase: 0,
    tableBase: 0,
    memory: new WebAssembly.Memory({
        initial: 256
    }),
    table: new WebAssembly.Table({
        initial: 2,
        element: 'anyfunc'
    }),
    abort: () => {
        throw 'abort';
    }
}
/**
 * 連接wasm和js的膠水代碼
 * @param {String} path wasm 文件路徑
 */
function loadWebAssembly(path) {
    return fetch(path)
        .then(response => response.arrayBuffer())
        .then(buffer => WebAssembly.compile(buffer))
        .then(module => {
            // 建立 WebAssembly 實例
            return new WebAssembly.Instance(module, {env: env})
        })
}
loadWebAssembly('./dist/module.optimized.wasm')
    .then(instance => {
        const {
           sqart
        } = instance.exports
        console.log("5 sqart is: ", sqart(5));
})
複製代碼

在index.html裏調用封裝好的wasm模塊,

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>webassembly</title>
  </head>
  <body>
    <div id="box">
      webassembly test
    </div>
  </body>
 
  <script src="./index.js"></script>
</html>
複製代碼

在瀏覽器中打開index.html,能夠看到控制檯裏已經打印出結果:

Webassembly和Javascript的速度對比

已經知道Webassembly在nodeJs和瀏覽器中如何使用,那是否是能夠知足我對性能提高的要求呢,我須要作一下速度對比測試。計算斐波拉切數列是一個不錯的測速方式,在src/index.ts添加計算斐波拉切數列的方法,index.ts代碼以下:

export function sqart (a: number): number {
    return a * a;
}
export function fibonacci (n: number): number {
    if ( n <= 2 ) {
        return 1;
    }
    return fibonacci(n - 2) + fibonacci(n - 1);
}
複製代碼

在根目錄執行npm run build從新生成wasm代碼。 看一下在nodeJs中的速度對比,這須要對nodejs/index.js的代碼作一下改造,改造後的代碼以下:

var myModule = require("./module.js");

function fibonacciJS(n) {
    if ( n <= 2 ) {
        return 1;
    }
    return fibonacciJS(n - 2) + fibonacciJS(n - 1);
} 

const startTime = Date.now();

myModule.fibonacci(50);

console.log("wasm fibonacci(45) time is: ", `${Date.now() - startTime}ms` );

const jstartTime = Date.now();

fibonacciJS(50);

console.log("js fibonacci(45) time is: ", `${Date.now() - jstartTime}ms` );
複製代碼

運行結果以下:

通過對斐波拉切數列第45個,第48個,第50個數的計算耗時,明顯能看出來wasm的運行速度比js快了近30%
在瀏覽器中測試結果跟在nodeJs中的結果相似,這裏再也不贅述,有興趣的同窗能夠本身嘗試一下。 由此能夠得出結論,Webassembly能夠知足我對驗證碼識別程序的性能改造要求,改造完成的結果也會第一時間向你們分享。

寫在最後

有聲音說Webassembly能夠取代javascript,可是我的認爲短期內javascript是不可替代的,Webassembly只是javascript的一個補充和完善,它將更多的編程語言帶到了web中。 最近也準備把咱們java大神寫的UA識別神器(對市面所有的UA識別準確率達到90%以上)轉換成wasm模塊,而後前端直接調用,之後就不須要爲了識別一個UA向服務端端發一次請求,沒有Webassembly以前,這是不敢想的。 Webassembly已是一個標準並被四大瀏覽器廠商積極支持,雖然它如今還有一些不完美的地方,好比加載須要寫膠水代碼。可是隨着時間的推移,它會愈來愈完善,web的性能會愈來愈高,將來咱們可能會進入一個告別安裝應用的時代,全部的應用都變成web應用,即開即用,但願這一天早日到來吧。

相關文章
相關標籤/搜索