在 NodeJS 中体验 WebAssembly技术(译)

Getting Started With WebAssembly in Node.jsWebAssembly 作为一个门新的语言,已经得到了许多 Javascript 引擎的支持。WebAssembly的目标是为了让 C 和 C++ 这样的编译型语言更容易地在浏览器上运行。…


原文地址: Getting Started With WebAssembly in Node.js

WebAssembly 作为一个门新的语言,已经得到了许多 Javascript 引擎的支持。WebAssembly的目标是为了让 C 和 C++ 这样的编译型语言更容易地在浏览器上运行。而让我感到激动的特性是计算性能和内存操作上的优化,这样让 Javascript 可以实现更为快速的浮点数计算而不用等到 TC39 方案的到来。在这里,借助于 NodeJS 我将会为你展示一些初级的 WebAssembly 示例。并进行一些基本的测试示例来显示其在性能方面的影响作用。

注:文中的所有只在 Node 7.2.1 中进行了测试,并开启了 –expose-wasm参数,其它环境可能无法运行。

通过开启 –expose-wasm参数,在 NodeJS 就可以访问全局对象 Wasm, 通过它可以来创建 WebAssembly 模块。

$ ~/Workspace/node-v7.2.1-linux-x64/bin/node --expose-wasm
> Wasm
{ verifyModule: [Function],
  verifyFunction: [Function],
  instantiateModule: [Function],
  experimentalVersion: 11 }
>

通过 Wasm.instantiateModule() 和 Uint8Array 来创建 WebAssembly 模块。

$ ~/Workspace/node-v7.2.1-linux-x64/bin/node --expose-wasm
> Wasm.instantiateModule(new Uint8Array([0x00, 0x61, 0x73, 0x6d, 0x0b, 0x00, 0x00, 0x00]));
{}
>

为了创建一个最基本的 WebAssembly 模块,你需要传递一组 16进制的数据给 instaniateModule 来得到,如上创建的是一个最小的 WebAssembly 模块,因为每一个 .wasm 文件都必须以这一组16进制数据开始。

两个数字求和

有许多的编译工具可以直接将 C, C++ 和 Rust 代码编译成 WebAssembly,这样我们就不用手写字节码了。也存在一种名为’WebAssembly AST'(简称wast)的中间代码格式,如下为一个简单的支持两个参数的求和函数 wast 代码。

(module
  (func $addTwo (param i32 i32) (result i32)
    (i32.add
      (get_local 0)
      (get_local 1)))
  (export "addTwo" $addTwo))

你可以使用这个在线工具将 wast 代码转换为 wasm 二进制代码。你也可以直接从这里下载 .wasm源码。

接下来如何用 Node.js 来运行 .wasm 文件呢?为了使用 .wasm文件,你需要通过文件模块将 .wasm 文件转换成 ArrayBuffer 格式。

const fs = require('fs');
const buf = fs.readFileSync('./addTwo.wasm');
const lib = Wasm.instantiateModule(toUint8Array(buf)).exports;

// `Wasm` does **not** understand node buffers, but thankfully a node buffer
// is easy to convert to a native Uint8Array.
function toUint8Array(buf) {
  var u = new Uint8Array(buf.length);
  for (var i = 0; i < buf.length; ++i) {
    u[i] = buf[i];
  }
  return u;
}

console.log(lib.addTwo(2, 2)); // Prints '4'
console.log(lib.addTwo.toString()); // Prints 'function addTwo() { [native code] }'

上面的 addTwo 方法与原生的 Javascript 方法相比,性能如何呢?如下为我们的测试结果:

const fs = require('fs');
const buf = fs.readFileSync('./addTwo.wasm');
const lib = Wasm.instantiateModule(toUint8Array(buf)).exports;

const Benchmark = require('benchmark');

const suite = new Benchmark.Suite;

suite.
  add('wasm'function() {
    lib.addTwo(2, 2);
  }).
  add('js'function() {
    addTwo(2, 2);
  }).
  on('cycle'function(event) {
    console.log(String(event.target));
  }).
  on('complete'function() {
    console.log('Fastest is ' + this.filter('fastest').map('name'));
  }).
  run();

function addTwo(a, b) {
  return a + b;
}

function toUint8Array(buf) {
  var u = new Uint8Array(buf.length);
  for (var i = 0; i < buf.length; ++i) {
    u[i] = buf[i];
  }
  return u;
}
$ ~/Workspace/node-v7.2.1-linux-x64/bin/node --expose-wasm ./addTwo.js
4
wasm x 43,497,742 ops/sec ±0.77% (88 runs sampled)
js x 66,021,200 ops/sec ±1.28% (83 runs sampled)
Fastest is js

阶乘

从上面的例子,我们可以看到 WebAssembly 并没有显示出性能上的优势。接下来我们进行阶乘计算来进一步的测试:

(module
  (func $fac (param i32) (result i32)
    (if (i32.lt_s (get_local 0) (i32.const 1))
      (then (i32.const 1))
      (else
        (i32.mul
          (get_local 0)
          (call $fac
            (i32.sub
              (get_local 0)
              (i32.const 1)))))))
  (export "fac" $fac))

下面是计算 100!的测试比较结果。

const fs = require('fs');
const buf = fs.readFileSync('./factorial.wasm');
const lib = Wasm.instantiateModule(toArrayBuffer(buf)).exports;

const Benchmark = require('benchmark');

const suite = new Benchmark.Suite;

suite.
  add('wasm'function() {
    lib.fac(100);
  }).
  add('js'function() {
    fac(100);
  }).
  on('cycle'function(event) {
    console.log(String(event.target));
  }).
  on('complete'function() {
    console.log('Fastest is ' + this.filter('fastest').map('name'));
  }).
  run();

function fac(n) {
  if (n <= 0) {
    return 1;
  }
  return n * fac(n - 1);
}

function toArrayBuffer(buf) {
  var ab = new ArrayBuffer(buf.length);
  var view = new Uint8Array(ab);
  for (var i = 0; i < buf.length; ++i) {
    view[i] = buf[i];
  }
  return ab;
}
$ ~/Workspace/node-v7.2.1-linux-x64/bin/node --expose-wasm ./factorial.js
wasm x 2,484,967 ops/sec ±2.09% (87 runs sampled)
js x 1,088,426 ops/sec ±2.63% (80 runs sampled)
Fastest is wasm

这里我们可以看到,因为计算的复杂度上升,wasm 的优势就显示出来了。

下一步?

从上面的测试例子可以看到通过 WebAssembly 可以很好的优化JS代码,不过这里的测试例子还比较简单,不能覆盖很多的情况,同时 WebAssembly 还并没有推出稳定版本,所以不要莽撞的在应用中使用 WebAssembly。但是我们可以提前试用一下,尤其已经可以在NodeJS中试用了。


原文始发于微信公众号(前端有道):在 NodeJS 中体验 WebAssembly技术(译)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/90292.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!