Open chenshenhai opened 5 years ago
前段时间开发图像处理工具Pictool后,遇到高频的计算瓶颈。在寻找高频计算的前端能力解决方案过程中,入门学习了一下 WebAssembly 在前端中的应用。入门的过程中踩了不少坑,例如使用AssemblyScript 开发wasm时候,发现 npm 包 assemblyscript 已经不维护了,需要自己人工添加成从Github 仓库引用assemblyscript 的npm模块。
AssemblyScript
wasm
npm
assemblyscript
Github
同时网上很多教程已经有点不同步,很多按照教程步骤后实现的代码跑不起来。最后参考原有网上的教程,一步步踩坑,实现了demo,同时也写下这篇文章作为笔记!
在前端主要的优势有
TypeScript
如果想更快速尝试,可以直接去该 demo 仓库获取源码使用。
https://github.com/chenshenhai/assemblyscript-demo
由于 AssemblyScript 的 npm 官方模块已经停止维护,所以AssemblyScript的模块需要从Github 来源安装。
在package.json的依赖加入 AssemblyScript 模块的 Github 来源
package.json
./package.json
{ // ... "devDependencies": { "assemblyscript": "github:assemblyscript/assemblyscript" // ... } }
再执行 npm install 从 Github 下载该模块到本地 node_module中
npm install
node_module
编写一个 斐波那契数列 函数
斐波那契数列
在 demo 的目录 ./src/index.ts 中
./src/index.ts
export function fib(num: i32): i32 { if (num === 1 || num === 2) { return 1; } else { return fib(num - 1) + fib(num - 2) } }
在 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/
├── dist │ ├── module.optimized.wasm │ ├── module.optimized.wasm.map │ ├── module.optimized.wat │ ├── module.untouched.wasm │ ├── module.untouched.wasm.map │ └── module.untouched.wat
在 ./example/node/module.js 文件中,封装wasm的CommonJS使用模块
./example/node/module.js
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 使用
const mod = require('./module'); const result = mod.fib(40); console.log(result);
执行 Node.js 的 wasm 引用
输出结果会是
102334155
在 ./example/browser/ 目录下部署浏览器访问的服务
./example/browser/
├── dist │ ├── module.optimized.wasm │ └── module.untouched.wasm ├── example │ ├── browser │ │ ├── demo.js │ │ ├── index.html │ │ └── server.js
临时浏览器可访问的服务,这里用 koa 来搭建服务
koa
具体实现在 ./example/browser/server.js 文件中
./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: http://127.0.0.1:3000/example/browser/index.html') })
浏览器使用 wasm 模块
具体实现在 ./example/browser/demo.js 文件中实现
./example/browser/demo.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 result = mod.instance.exports.fib(40); console.log(result) });
访问页面在 ./example/browser/index.html 中
./example/browser/index.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>demo</title> </head> <body> </body> <script src="demo.js"></script> </html>
启动服务
node ./example/browser/server.js
浏览器访问页面
http://127.0.0.1:3000/example/browser/index.html
浏览器会出现结果
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`)
833 ms
597 ms
30%
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 + `<p>${logWasm}</p>` // 打印 浏览器环境下 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 + `<p>${logPure}</p>` console.log(logPure);
884 ms
612 ms
从上述 Node.js 和 Chrome 环境下运行 wasm 和 原生js 的对比中,wasm的在高频计算的场景下,耗时的确是比原生js低,同时都是接近 30% 的计算性能提升。
js
npm 上assemblyscript 目前版本是 0.8.1 和 github仓库保持同步。
前言
前段时间开发图像处理工具Pictool后,遇到高频的计算瓶颈。在寻找高频计算的前端能力解决方案过程中,入门学习了一下 WebAssembly 在前端中的应用。入门的过程中踩了不少坑,例如使用
AssemblyScript
开发wasm
时候,发现npm
包assemblyscript
已经不维护了,需要自己人工添加成从Github
仓库引用assemblyscript
的npm
模块。同时网上很多教程已经有点不同步,很多按照教程步骤后实现的代码跑不起来。最后参考原有网上的教程,一步步踩坑,实现了demo,同时也写下这篇文章作为笔记!
WebAssembly
什么是 WebAssembly
WebAssembly 优势
在前端主要的优势有
WebAssembly 前端能力现状
什么是 AssemblyScript
AssemblyScript
是TypeScript
的一个子集TypeScript
语法编写功能编译成wasm
,对前端来说比较友好。快速开始
demo源码地址
如果想更快速尝试,可以直接去该 demo 仓库获取源码使用。
https://github.com/chenshenhai/assemblyscript-demo
安装 AssemblyScript
由于
AssemblyScript
的npm
官方模块已经停止维护,所以AssemblyScript
的模块需要从Github
来源安装。在
package.json
的依赖加入AssemblyScript
模块的Github
来源./package.json
再执行
npm install
从Github
下载该模块到本地node_module
中编写功能代码
编写一个
斐波那契数列
函数在 demo 的目录
./src/index.ts
中编译
在
package.json
编写编译脚本./package.json
在项目根目录开始执行编译
后面会在
./dist/
目录下产生编译后的几种wasm
文件格式Node.js 使用
在
./example/node/module.js
文件中,封装wasm
的CommonJS
使用模块Node.js 使用
执行 Node.js 的
wasm
引用输出结果会是
浏览器使用
在
./example/browser/
目录下部署浏览器访问的服务临时浏览器可访问的服务,这里用
koa
来搭建服务具体实现在
./example/browser/server.js
文件中浏览器使用
wasm
模块具体实现在
./example/browser/demo.js
文件中实现访问页面在
./example/browser/index.html
中启动服务
浏览器访问页面
http://127.0.0.1:3000/example/browser/index.html
浏览器会出现结果
性能测试
Node.js 对比测试
测试结果
833 ms
597 ms
斐波那契数列
比 js 执行快了接近30%
浏览器对比测试
测试结果
884 ms
612 ms
斐波那契数列
比 js 执行快了也是接近30%
从上述 Node.js 和 Chrome 环境下运行
wasm
和 原生js
的对比中,wasm
的在高频计算的场景下,耗时的确是比原生js
低,同时都是接近30%
的计算性能提升。参考资料