最近在破解网站验证码的时候,图像识别速度上遇到一点瓶颈。按照我现在的代码,从获取到验证码图片到输出正确验证码字符串需要等待3秒的时间,但是3秒之后破解完黄花菜都凉了,所以我想有没有什么方法让程序执行的快一点,最后目光聚焦在了Webassembly,所以入门学习了一下Webassembly。
Webassembly是什么?
Webassembly顾名思义web+assembly,web版的汇编语言,其实它并不是一种语言,它只是为高级语言(诸如C、C++和Java)提供一个高效的编译目标。之所以和汇编能扯上关系,是因为它是接近计算机机器码的二进制字节码,并且可以在现代浏览器直接运行。
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的支持非常友好。
再看一下具体浏览器支持情况:
从上面这张图可以看出各大浏览器对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,目录结构入下图所示:- 安装AssemblyScript 去npm搜了一下AssemblyScript模块,发现AssemblyScript 的 npm 官方模块已经停止维护(搞不懂为什么会停止维护),那只好直接从 Github 安装AssemblyScript 的模块。
在 package.json 的依赖加入 AssemblyScript 模块的 Github 来源。
"devDependencies": {
"assemblyscript": "github:assemblyscript/assemblyscript"
}
执行cnpm install,等待安装完成用asc来看一下是否安装成功。如果显示asc的使用命令行,则说明安装成功。
也可以将AssemblyScript的Github库clone到本地,使用npm link的方式来全局安装AssemblyScript模块。- 写原始代码 在src目录下新建一个index.ts,写一个计算平方和的方法吧
export function sqart (a: number): number {
return a * a;
}
- 编译生成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文本文件。编译完成的目录结构如图所示:
- 在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模块,
var myModule = require("./module.js");
console.log("3 sqart is: ", myModule.sqart(3));
在根目录下运行node ./nodejs/index.js,输出下面结果:
- 在浏览器中使用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应用,即开即用,希望这一天早日到来吧。