原文链接: https://github.com/chenshenhai/blog/issues/38
前段时间开发图像处理工具 Pictool [1] 后,遇到图像处理的高频的计算瓶颈。在寻找高频计算的前端能力解决方案过程中,入门学习了一下 AssemblyScript [2] 在前端中的应用。
入门的过程中踩了不少坑,例如使用 AssemblyScript [2] 开发 wasm 时候,发现 npm 包 assemblyscript 已经不维护了,需要自己人工添加成从 Github 仓库引用 assemblyscript 的 npm 模块。
与此同时,网上很多教程说明已经有点 “过时” 了,大部分按照教程步骤后实现的代码都运行不起来。最后参考原有网上的教程,一步步踩坑,实现了demo,同时也写下这篇文章作为笔记!
图片来源自网络
在前端主要的优势有
注:如想更快速尝试,可以直接去该 demo 仓库获取源码使用。 github.com/chenshenhai/assemblyscript-demo
1、安装 AssemblyScript
由于 AssemblyScript 的 npm 官方模块已经停止维护,所以 AssemblyScript 的模块需要从 Github 来源安装。
在 package.json 的依赖加入 AssemblyScript 模块的 Github 来源
{ // ... "devDependencies": { "assemblyscript": "github:assemblyscript/assemblyscript" // ... }}
再执行 npm install 从 Github 下载该模块到本地 node_module 中
2、编写功能代码
编写一个 斐波那契数列 函数
在 demo 的目录 ./src/index.ts 中
export function fib(num: i32): i32 { if (num === 1 || num === 2) { return 1; } else { return fib(num - 1) + fib(num - 2) }}
3、编译成wasm
在 package.json 编写编译脚本
{ // ... "scripts": { "build": "npm run build:untouched && npm run build:optimized", "build:untouched": "./node_modules/assemblyscript/bin/asc src/index.ts -t dist/module.untouched.wat -b dist/module.untouched.wasm --validate --sourceMap --measure", "build:optimized": "./node_modules/assemblyscript/bin/asc src/index.ts -t dist/module.optimized.wat -b dist/module.optimized.wasm --validate --sourceMap --measure --optimize" // ... },}
在项目根目录开始执行编译
npm run build
编译后会在 ./dist/ 目录下产生编译后的几种 wasm 文件格式
├── dist│ ├── module.optimized.wasm│ ├── module.optimized.wasm.map│ ├── module.optimized.wat│ ├── module.untouched.wasm│ ├── module.untouched.wasm.map│ └── module.untouched.wat
4、Node.js使用
在 ./example/node/module.js 文件中,封装 wasm 的 CommonJS使用模块。
const fs = require('fs');const path = require('path');
const wasmFile = fs.readFileSync(path.join(__dirname, '..', '..', './dist/module.optimized.wasm'))
const wasm = new WebAssembly.Module(wasmFile, {});
module.exports = new WebAssembly.Instance(wasm, { env: { memoryBase: 0, tableBase: 0, memory: new WebAssembly.Memory({ initial: 256, maximum: 512, }), table: new WebAssembly.Table({ initial: 0, maximum: 0, element: 'anyfunc', }), abort: console.log, },}).exports;
Node.js 使用 wasm 的封装模块
const mod = require('./module');
const result = mod.fib(40);console.log(result);
执行结果会出现
> 102334155
3、浏览器使用
在 ./example/browser/ 目录下部署浏览器访问的服务
├── dist│ ├── module.optimized.wasm│ └── module.untouched.wasm├── example│ ├── browser│ │ ├── demo.js│ │ ├── index.html│ │ └── server.js
临时浏览器可访问的服务,这里用 koa
来搭建服务。
具体实现在 ./example/browser/server.js 文件中
const Koa = require('koa')const path = require('path')const static = require('koa-static')
const app = new Koa()
const staticPath = './../../'
app.use(static( path.join( __dirname, staticPath)))
app.listen(3000, () => { console.log('[INFO]: server starting at port 3000'); console.log('open: ')})
浏览器使用 wasm 模块
具体实现在 ./example/browser/demo.js 文件中实现
fetch('/dist/module.optimized.wasm') .then(res => res.arrayBuffer()) .then((wasm) => { return new WebAssembly.instantiate(wasm, { env: { memoryBase: 0, tableBase: 0, memory: new WebAssembly.Memory({ initial: 256, maximum: 512, }), table: new WebAssembly.Table({ initial: 0, maximum: 0, element: 'anyfunc', }), abort: console.log, }, }) }).then(mod => { const result = mod.instance.exports.fib(40); console.log(result) });
启动服务,访问页面后也是显示了结果
> 102334155
1、Node.js 环境对比wasm和原生js
测试代码
const mod = require('./module');
const start = Date.now();mod.fib(40)// 打印 Node.js 环境下 wasm 计算 斐波那契数列 参数为40 的耗时结果console.log(`nodejs-wasm time consume: ${Date.now() - start} ms`)
// 原生Node.js实现的 斐波那契数列 函数function pureFib(num) { if (num === 1 || num === 2) { return 1; } else { return pureFib(num - 1) + pureFib(num - 2) }}
const startPure = Date.now()pureFib(40);// 打印 Nodejs环境下 原生js 计算 斐波那契数列 参数为40 的耗时结果console.log(`nodejs-js time consume: ${Date.now() - startPure} ms`)
测试结果
Node.js环境下,原生js 执行耗时 833 ms
Node.js环境下,wasm 执行耗时 597 ms
对比下来,在Node.js 环境中, wasm 计算 斐波那契数列 比 原生js 执行快了接近 30%
2、浏览器环境对比wasm和原生js
浏览器测试代码
const $body = document.querySelector('body');
fetch('/dist/module.optimized.wasm') .then(res => res.arrayBuffer()) .then((wasm) => { return new WebAssembly.instantiate(wasm, { env: { memoryBase: 0, tableBase: 0, memory: new WebAssembly.Memory({ initial: 256, maximum: 512, }), table: new WebAssembly.Table({ initial: 0, maximum: 0, element: 'anyfunc', }), abort: console.log, }, }) }).then(mod => {
const start = Date.now(); mod.instance.exports.fib(40); const logWasm = `browser-wasm time consume: ${Date.now() - start} ms`; $body.innerHTML = $body.innerHTML + `${logWasm}
` // 打印 浏览器环境下 wasm 计算 斐波那契数列 参数为40 的耗时结果 console.log(logWasm) });
// 打印 浏览器环境下 原生js 计算 斐波那契数列 参数为40 的耗时结果 function pureFib(num) { if (num === 1 || num === 2) { return 1; } else { return pureFib(num - 1) + pureFib(num - 2) } } const startPure = Date.now() pureFib(40); const logPure = `browser-js time consume: ${Date.now() - startPure} ms`; $body.innerHTML = $body.innerHTML + `${logPure}
` console.log(logPure);
测试结果
Chrome环境下,原生js 执行耗时 884 ms
Chrome环境下,wasm 执行耗时 612 ms
对比下来,在Chrome浏览器中 wasm 计算 斐波那契数列 比 原生js 执行快了也是接近 30%
从上述 Node.js 和 Chrome 环境下运行 wasm 和 原生js 的对比中,wasm的在高频计算的场景下,耗时的确是比原生js低,同时都是接近 30% 的计算性能提升。
参考资料
[1] Pictool -用TypeScript写了个低配版H5美图工具 : https://github.com/chenshenhai/blog/issues/37
[2] IBM开发者文档: WebAssembly 现状与实战 : https://www.ibm.com/developerworks/cn/web/wa-lo-webassembly-status-and-reality/index.html
[3] 奇舞周刊: 20分钟上手 webAssembly: https://juejin.im/post/5b7a3f3cf265da43606e9109
[4] WebAssembly.LinkError()报错 : https://cunzaizhuyi.github.io/WebAssembly-LinkError/