Board logo

标题: WebAssembly 现状与实战(2) [打印本页]

作者: look_w    时间: 2018-11-22 19:29     标题: WebAssembly 现状与实战(2)

WebAssembly                相关文件格式前面提到了 WebAssembly 的二进制文件格式 wasm,这种格式的文件人眼无法阅读,为了阅读 WebAssembly                文件的逻辑,还有一种文本格式叫 wast; 以前面讲到的计算斐波那契序列的模块为例,对应的 wast 文件如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func $src/asm/module/f (param f64) (result f64)
(local i32)
  get_local 0
  f64.const 1
  f64.eq
  tee_local 1
  if i32
    get_local 1
  else
    get_local 0
    f64.const 2
    f64.eq
  end
  i32.const 1
  i32.and
  if
    f64.const 1
    return
  end
  get_local 0
  f64.const 1
  f64.sub
  call 0
  get_local 0
  f64.const 2
  f64.sub
  call 0
  f64.add
end




这和汇编语言非常像,里面的 f64 是数据类型,f64.eq f64.sub f64.add 则是 CPU 指令。
为了把二进制文件格式 wasm 转换成人眼可见的 wast 文本,需要安装 WebAssembly 二进制工具箱 , 在 Mac 系统下可通过 brew                install WABT 安装,安装成功后可以通过命令 wasm2wast f.wasm 获得 wast;除此之外还可以通过 wast2wasm                f.wast -o f.wasm 逆向转换回去。
WebAssembly                相关工具除了前面提到的 WebAssembly 二进制工具箱,WebAssembly 社区还有以下常用工具:
WebAssembly JS                API目前 WebAssembly 只能通过 JS 去加载和执行,但未来在浏览器中可以通过像加载 JS 那样 <script                src='f.wasm'></script> 去加载和执行 WebAssembly,下面来详细介绍如何用 JS 调                WebAssembly。
JS 调 WebAssembly 分为 3 大步:加载字节码 > 编译字节码 > 实例化,获取到                WebAssembly 实例后就可以通过 JS 去调用了,以上 3 步具体的操作是:
其中的第 2、3 步可以合并一步完成,前面提到的   就做了这两个事情。
1
2
3
WebAssembly.instantiate(bytes).then(mod=>{
  mod.instance.f(50);
})




WebAssembly 调                JS之前的例子都是用 JS 去调用 WebAssembly 模块,但是在有些场景下可能需要在 WebAssembly 模块中调用浏览器                API,接下来介绍如何在 WebAssembly 中调用 JS。
WebAssembly.instantiate 函数支持第二个参数                WebAssembly.instantiate(bytes,importObject),这个 importObject 参数的作用就是 JS 向                WebAssembly 传入 WebAssembly 中需要调用 JS 的 JS 模块。举个具体的例子,改造前面的计算斐波那契序列在                WebAssembly 中调用 Web 中的 window.alert 函数把计算结果弹出来,为此需要改造加载 WebAssembly 模块的 JS                代码:
1
2
3
4
5
6
7
WebAssembly.instantiate(bytes,{
  window:{
    alert:window.alert
  }
}).then(mod=>{
  mod.instance.f(50);
})




对应的还需要修改 AssemblyScript 编写的源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 声明从外部导入的模块类型
declare namespace window {
    export function alert(v: number): void;
}

function _f(x: number): number {
    if (x == 1 || x == 2) {
        return 1;
    }
    return _f(x - 1) + _f(x - 2)
}

export function f(x: number): void {
    // 直接调用 JS 模块
    window.alert(_f(x));
}




修改以上 AssemblyScript 源码后重新用 asc 通过命令 asc f.ts 编译后输出的 wast 文件比之前多了几行:
1
2
3
4
5
6
(import "window" "alert" (func $src/asm/module/window.alert (type 0)))

(func $src/asm/module/f (type 0) (param f64)
    get_local 0
    call $src/asm/module/_f
    call $src/asm/module/window.alert)




多出的这部分 wast 代码就是在 AssemblyScript 中调用 JS 中传入的模块的逻辑。
除了以上常用的 API 外,WebAssembly 还提供一些 API,你可以通过这个 去查看所有 WebAssembly JS API 的细节。
不止于浏览器WebAssembly 作为一种底层字节码,除了能在浏览器中运行外,还能在其它环境运行。
直接执行 wasm                二进制文件前面提到的 Binaryen 提供了在命令行中直接执行 wasm 二进制文件的工具,在 Mac 系统下通过 brew install binaryen                安装成功后,通过 wasm-shell f.wasm 文件即可直接运行。
在 Node.js                中运行目前 V8 JS 引擎已经添加了对 WebAssembly 的支持,Chrome 和 Node.js 都采用了 V8 作为引擎,因此                WebAssembly 也可以运行在 Node.js 环境中;
V8 JS 引擎在运行 WebAssembly 时,WebAssembly 和 JS 是在同一个虚拟机中执行,而不是 WebAssembly                在一个单独的虚拟机中运行,这样方便实现 JS 和 WebAssembly 之间的相互调用。
要让上面的例子在 Node.js 中运行,可以使用以下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const fs = require('fs');

function toUint8Array(buf) {
    var u = new Uint8Array(buf.length);
    for (var i = 0; i < buf.length; ++i) {
        u = buf;
    }
    return u;
}

function loadWebAssembly(filename, imports) {
    // 读取 wasm 文件,并转换成 byte 数组
    const buffer = toUint8Array(fs.readFileSync(filename));
    // 编译 wasm 字节码到机器码
    return WebAssembly.compile(buffer)
        .then(module => {
            // 实例化模块
            return new WebAssembly.Instance(module, imports)
        })
}

loadWebAssembly('../temp/assembly/module.wasm')
    .then(instance => {
        // 调用 f 函数计算
        console.log(instance.exports.f(10))
    });




在 Nodejs 环境中运行 WebAssembly 的意义其实不大,原因在于 Nodejs 支持运行原生模块,而原生模块的性能比                WebAssembly 要好。 如果你是通过 C、Rust 去编写 WebAssembly,你可以直接编译成 Nodejs                可以调用的原生模块。
WebAssembly                展望从上面的内容可见 WebAssembly 主要是为了解决 JS 的性能瓶颈,也就是说 WebAssembly                适合用于需要大量计算的场景,例如:
总结WebAssembly 标准虽然已经定稿并且得到主流浏览器的实现,但目前还存在以下问题:
总之现在的 WebAssembly 还不算成熟,如果你的团队没有不可容忍的性能问题,那现在使用 WebAssembly 到产品中还不是时候,                因为这可能会影响到团队的开发效率,或者遇到无法轻易解决的坑而阻塞开发。




欢迎光临 电子技术论坛_中国专业的电子工程师学习交流社区-中电网技术论坛 (http://bbs.eccn.com/) Powered by Discuz! 7.0.0