Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

浅尝 WebAssembly 在Node.js和浏览器的性能对比 #38

Open
chenshenhai opened this issue Aug 25, 2019 · 1 comment
Open

浅尝 WebAssembly 在Node.js和浏览器的性能对比 #38

chenshenhai opened this issue Aug 25, 2019 · 1 comment
Labels
wasm WebAssembly

Comments

@chenshenhai
Copy link
Owner

chenshenhai commented Aug 25, 2019

前言

前段时间开发图像处理工具Pictool后,遇到高频的计算瓶颈。在寻找高频计算的前端能力解决方案过程中,入门学习了一下 WebAssembly 在前端中的应用。入门的过程中踩了不少坑,例如使用AssemblyScript 开发wasm时候,发现 npmassemblyscript 已经不维护了,需要自己人工添加成从Github 仓库引用assemblyscriptnpm模块。

同时网上很多教程已经有点不同步,很多按照教程步骤后实现的代码跑不起来。最后参考原有网上的教程,一步步踩坑,实现了demo,同时也写下这篇文章作为笔记!

WebAssembly

什么是 WebAssembly

  • 计算机是不能直接识别运行高级语言(C/C++, Java, JavaScript等)。
  • 计算机能读懂是0和1的电子元件信号,对应到运行的机器码。
  • 在前端浏览器领域里,JS是解释执行,也就是运行到哪就解释成机器码让计算机读懂并执行,在高频计算性能上有一定的瓶颈。
  • WebAssembly 字节码是接近计算机能识别的机器码,只要运行环境有对应的虚拟机,能快速加载运行。

WebAssembly 优势

在前端主要的优势有

  • 体积小
  • 加载快
  • 兼容强

WebAssembly 前端能力现状

  • Node.js 目前已经支持了 WebAssembly
  • 大部分主流浏览器厂商也支持了 WebAssembly

什么是 AssemblyScript

  • AssemblyScriptTypeScript 的一个子集
  • 可以用 TypeScript 语法编写功能编译成 wasm,对前端来说比较友好。

快速开始

demo源码地址

如果想更快速尝试,可以直接去该 demo 仓库获取源码使用。

https://github.com/chenshenhai/assemblyscript-demo

安装 AssemblyScript

由于 AssemblyScriptnpm 官方模块已经停止维护,所以AssemblyScript的模块需要从Github 来源安装。

wasm-003

package.json的依赖加入 AssemblyScript 模块的 Github 来源

./package.json

{
  // ...
  "devDependencies": {
    "assemblyscript": "github:assemblyscript/assemblyscript"
    // ...
  }
}

再执行 npm installGithub 下载该模块到本地 node_module

npm install

编写功能代码

编写一个 斐波那契数列 函数

在 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)
  }
}

编译

package.json 编写编译脚本

./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

Node.js 使用

./example/node/module.js 文件中,封装wasmCommonJS使用模块

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/ 目录下部署浏览器访问的服务

├── 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: http://127.0.0.1:3000/example/browser/index.html')
})

浏览器使用 wasm 模块

具体实现在 ./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

<!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

浏览器会出现结果

102334155

性能测试

Node.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`)

测试结果

wasm-001

  • Node.js环境下,原生js 执行耗时 833 ms
  • Node.js环境下,wasm 执行耗时 597 ms
  • 对比下来,wasm 计算斐波那契数列 比 js 执行快了接近 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);

测试结果

wasm-002

  • Chrome浏览器环境下,原生js 执行耗时 884 ms
  • Chrome浏览器环境下,wasm 执行耗时 612 ms
  • 对比下来,wasm 计算斐波那契数列 比 js 执行快了也是接近 30%

从上述 Node.js 和 Chrome 环境下运行 wasm 和 原生js 的对比中,wasm的在高频计算的场景下,耗时的确是比原生js低,同时都是接近 30% 的计算性能提升。

参考资料

@chenshenhai chenshenhai changed the title 浅尝 Webassembly 在浏览器和Node.js的性能对比 浅尝 Webassembly 在Node.js和浏览器的性能对比 Aug 25, 2019
@chenshenhai chenshenhai changed the title 浅尝 Webassembly 在Node.js和浏览器的性能对比 浅尝 WebAssembly 在Node.js和浏览器的性能对比 Aug 25, 2019
@chenshenhai chenshenhai added the wasm WebAssembly label Aug 25, 2019
@ghost
Copy link

ghost commented Jan 22, 2020

npm 上assemblyscript 目前版本是 0.8.1 和 github仓库保持同步。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
wasm WebAssembly
Projects
None yet
Development

No branches or pull requests

1 participant